#![warn(missing_docs)]
#[cfg(test)]
#[allow(clippy::result_large_err)]
#[allow(clippy::len_zero)]
mod tests {
use decrust_core::backtrace::DecrustBacktrace as Backtrace;
use decrust_core::decrust::{
AstTraitImplementationFixGenerator, BorrowAfterMoveFixGenerator,
DiagnosticParameterExtractor, EnumParameterMatchFixGenerator,
GenericParamConflictFixGenerator, ImmutableBorrowFixGenerator, MatchPatternFixGenerator,
MismatchedTypeFixGenerator, MissingLifetimeFixGenerator, MissingReturnFixGenerator,
MissingSemicolonFixGenerator, MissingTraitImplFixGenerator, NotFoundFixGenerator,
PrivateFieldAccessFixGenerator, RegexParameterExtractor, StructParameterMatchFixGenerator,
UnnecessaryBracesFixGenerator, UnusedImportFixGenerator, UnusedVariableFixGenerator,
};
use decrust_core::types::{
DiagnosticResult, ErrorContext, ErrorLocation, ExtractedParameters, FixDetails,
FixGenerator, FixType,
};
use decrust_core::FixTemplate;
use decrust_core::{
AstMissingImportFixGenerator, AstUnusedCodeFixGenerator, Autocorrection,
ClosureCaptureLifetimeFixGenerator, ConfigMissingKeyFixGenerator, ConfigSyntaxFixGenerator,
DivisionByZeroFixGenerator, InvalidArgumentCountFixGenerator,
IoMissingDirectoryFixGenerator, IoPermissionFixGenerator, JsonParseFixGenerator,
MissingOkErrFixGenerator, NetworkConnectionFixGenerator, NetworkTlsFixGenerator,
QuestionMarkPropagationFixGenerator, RecursiveTypeFixGenerator,
ReturnLocalReferenceFixGenerator, RuntimePanicFixGenerator, UnnecessaryCloneFixGenerator,
UnnecessaryParenthesesFixGenerator, UnsafeUnwrapFixGenerator, UnstableFeatureFixGenerator,
UnusedMutFixGenerator, YamlParseFixGenerator,
};
use decrust_core::{AutocorrectableError, ParameterExtractor};
use decrust_core::{Decrust, DecrustError, OptionalError};
use std::path::PathBuf;
use regex::Regex;
#[test]
fn test_decrust_suggest_autocorrection_for_notfound() {
let decrust = Decrust::new();
let error = DecrustError::NotFound {
resource_type: "file".to_string(),
identifier: "/path/to/missing_file.txt".to_string(),
backtrace: Backtrace::generate(),
};
let autocorrection = error.suggest_autocorrection(&decrust, None);
assert!(
autocorrection.is_some(),
"Expected autocorrection for NotFound error"
);
if let Some(correction) = autocorrection {
assert_eq!(correction.fix_type, FixType::ExecuteCommand);
assert!(correction.description.contains("Resource type 'file'"));
assert!(correction.description.contains("/path/to/missing_file.txt"));
assert!(correction
.commands_to_apply
.iter()
.any(|cmd| cmd.contains("touch")));
}
}
#[test]
fn test_decrust_get_diagnostic_info() {
let diagnostic = DiagnosticResult {
primary_location: Some(ErrorLocation::new("src/main.rs", 42, 10, "main")),
expansion_trace: Vec::new(),
suggested_fixes: vec!["Replace `foo` with `bar`".to_string()],
original_message: Some("Invalid syntax".to_string()),
diagnostic_code: Some("E0001".to_string()),
};
let context =
ErrorContext::new("Error with diagnostic info").with_diagnostic_info(diagnostic);
let base_error = DecrustError::Io {
source: std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid data"),
path: Some(PathBuf::from("src/main.rs")),
operation: "parse".to_string(),
backtrace: Backtrace::generate(),
};
let error = DecrustError::WithRichContext {
context,
source: Box::new(base_error),
};
let diagnostic_info = error.get_diagnostic_info();
assert!(diagnostic_info.is_some(), "Expected diagnostic info");
if let Some(info) = diagnostic_info {
assert_eq!(info.suggested_fixes.len(), 1);
assert_eq!(info.suggested_fixes[0], "Replace `foo` with `bar`");
assert_eq!(info.diagnostic_code, Some("E0001".to_string()));
if let Some(location) = &info.primary_location {
assert_eq!(location.file, "src/main.rs");
assert_eq!(location.line, 42);
} else {
panic!("Expected primary location in diagnostic info");
}
}
}
#[test]
fn test_autocorrection_for_embedded_diagnostic() {
let decrust = Decrust::new();
let diagnostic = DiagnosticResult {
primary_location: Some(ErrorLocation::new("src/main.rs", 42, 10, "main")),
expansion_trace: Vec::new(),
suggested_fixes: vec!["Fix: add semicolon".to_string()],
original_message: Some("Missing semicolon".to_string()),
diagnostic_code: Some("E0001".to_string()),
};
let context = ErrorContext::new("Syntax error").with_diagnostic_info(diagnostic);
let base_error = DecrustError::Io {
source: std::io::Error::new(std::io::ErrorKind::InvalidData, "Parser error"),
path: Some(PathBuf::from("src/main.rs")),
operation: "parse".to_string(),
backtrace: Backtrace::generate(),
};
let error = DecrustError::WithRichContext {
context,
source: Box::new(base_error),
};
let autocorrection = error.suggest_autocorrection(&decrust, None);
assert!(
autocorrection.is_some(),
"Expected autocorrection from diagnostic info"
);
if let Some(correction) = autocorrection {
assert_eq!(correction.fix_type, FixType::TextReplacement);
assert!(correction
.description
.contains("Apply fix suggested by diagnostic tool"));
assert_eq!(correction.targets_error_code, Some("E0001".to_string()));
}
}
#[test]
fn test_parameter_extraction_from_various_errors() {
let decrust = Decrust::new();
let notfound_error = DecrustError::NotFound {
resource_type: "file".to_string(),
identifier: "/path/to/missing_file.txt".to_string(),
backtrace: Backtrace::generate(),
};
let notfound_params = decrust.extract_parameters(¬found_error);
assert!(notfound_params.confidence > 0.0);
assert_eq!(
notfound_params.values.get("resource_type"),
Some(&"file".to_string())
);
assert_eq!(
notfound_params.values.get("identifier"),
Some(&"/path/to/missing_file.txt".to_string())
);
let io_error = DecrustError::Io {
source: std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"),
path: Some(PathBuf::from("/path/to/file.txt")),
operation: "read".to_string(),
backtrace: Backtrace::generate(),
};
let io_params = decrust.extract_parameters(&io_error);
assert!(io_params.confidence > 0.0);
assert_eq!(io_params.values.get("operation"), Some(&"read".to_string()));
assert_eq!(
io_params.values.get("path"),
Some(&"/path/to/file.txt".to_string())
);
}
#[test]
fn test_regex_parameter_extractor() {
let extractor = RegexParameterExtractor::new();
let error = DecrustError::NotFound {
resource_type: "file".to_string(),
identifier: "/path/to/missing_file.txt".to_string(),
backtrace: Backtrace::generate(),
};
let _params = extractor.extract_parameters(&error);
let regex = Regex::new(r"Resource type '([^']+)' with identifier '([^']+)'").unwrap();
let message = "Resource type 'file' with identifier '/path/to/missing_file.txt'";
let captures = regex.captures(message).unwrap();
assert_eq!(captures.get(1).unwrap().as_str(), "file");
assert_eq!(
captures.get(2).unwrap().as_str(),
"/path/to/missing_file.txt"
);
}
#[test]
fn test_diagnostic_parameter_extractor() {
let extractor = DiagnosticParameterExtractor::new();
let diag_info = DiagnosticResult {
primary_location: Some(ErrorLocation::new("src/main.rs", 42, 10, "main")),
expansion_trace: vec![],
suggested_fixes: vec!["fix1".to_string()],
original_message: Some("Test error".to_string()),
diagnostic_code: Some("E0001".to_string()),
};
let error_context = ErrorContext::new("Test error").with_diagnostic_info(diag_info);
let error = DecrustError::Internal {
message: "Test error".to_string(),
source: OptionalError(None),
component: None,
backtrace: Backtrace::generate(),
}
.add_context(error_context);
let params = extractor.extract_parameters(&error);
assert!(params.confidence > 0.0);
assert_eq!(
params.values.get("file_path"),
Some(&"src/main.rs".to_string())
);
assert_eq!(params.values.get("line"), Some(&"42".to_string()));
assert_eq!(
params.values.get("diagnostic_code"),
Some(&"E0001".to_string())
);
}
#[test]
fn test_notfound_fix_generator() {
let generator = NotFoundFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("resource_type", "file");
params.add_parameter("identifier", "/path/to/missing_file.txt");
params.set_confidence(0.8);
let error = DecrustError::NotFound {
resource_type: "file".to_string(),
identifier: "/path/to/missing_file.txt".to_string(),
backtrace: Backtrace::generate(),
};
let fix = generator.generate_fix(&error, ¶ms, None);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ExecuteCommand);
assert!(fix.commands_to_apply.len() > 0);
assert!(fix.commands_to_apply[0].contains("mkdir"));
}
#[test]
fn test_fix_template_application() {
let template = FixTemplate::new(
"io_error_template",
"Fix I/O error template",
"I/O error during '{param1}' on path '{param2}'. Check file permissions and path validity.",
);
let mut params = std::collections::HashMap::new();
params.insert("param1".to_string(), "read".to_string());
params.insert("param2".to_string(), "/path/to/file.txt".to_string());
let result = template.apply(¶ms);
assert!(result.contains("read"));
assert!(result.contains("/path/to/file.txt"));
assert!(result.contains("I/O error during 'read' on path '/path/to/file.txt'"));
}
#[test]
fn test_unused_code_fix_generators() {
let generator = UnusedImportFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("param1", "std::io");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.9);
let source_context = "use std::io;\nuse std::fs;\n\nfn main() {\n // Code here\n}";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "imports".to_string(),
message: "unused import: `std::io`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Remove unused import"));
assert!(fix.commands_to_apply.len() > 0);
let mut params = ExtractedParameters::default();
params.add_parameter("param1", "io");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.9);
let source_context =
"use std::{io, fs, path::PathBuf};\n\nfn main() {\n // Code here\n}";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "imports".to_string(),
message: "unused import: `io`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Remove unused import"));
assert!(fix.commands_to_apply.len() > 0);
assert!(fix
.diff_suggestion
.as_ref()
.unwrap()
.contains("-use std::{io, fs, path::PathBuf};"));
let var_generator = UnusedVariableFixGenerator::new();
let mut var_params = ExtractedParameters::default();
var_params.add_parameter("param1", "value");
var_params.add_parameter("file_path", "src/main.rs");
var_params.add_parameter("line", "10");
var_params.set_confidence(0.9);
let var_source_context =
"fn main() {\n let value = 42;\n println!(\"Hello, world!\");\n}";
let var_fix = var_generator.generate_fix(
&DecrustError::Validation {
field: "variables".to_string(),
message: "unused variable: `value`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
&var_params,
Some(var_source_context),
);
assert!(var_fix.is_some());
let var_fix = var_fix.unwrap();
assert_eq!(var_fix.fix_type, FixType::TextReplacement);
assert!(var_fix.description.contains("unused variable"));
assert!(var_fix.commands_to_apply.len() > 0);
let fix = var_generator.generate_fix(
&DecrustError::Validation {
field: "variables".to_string(),
message: "unused variable: `value`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix
.description
.contains("Add underscore prefix to unused variable"));
assert!(fix.commands_to_apply.len() > 0);
let mut params = ExtractedParameters::default();
params.add_parameter("param1", "x");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.9);
let source_context = "fn process(x: i32, y: i32) -> i32 {\n y * 2\n}";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "variables".to_string(),
message: "unused variable: `x`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
println!("Actual description: {}", fix.description);
assert!(fix.commands_to_apply.len() > 0);
let mut params = ExtractedParameters::default();
params.add_parameter("param1", "val");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.9);
let source_context = "match result {\n Ok(val) => println!(\"Success!\"),\n Err(e) => println!(\"Error: {}\", e),\n}";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "variables".to_string(),
message: "unused variable: `val`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
println!("Actual description: {}", fix.description);
assert!(fix.commands_to_apply.len() > 0);
println!("Actual diff suggestion: {:?}", fix.diff_suggestion);
}
#[test]
fn test_unnecessary_braces_fix_generator() {
let generator = UnnecessaryBracesFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("message", "unnecessary braces around single import");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.9);
let source_context =
"use std::time::{Duration};\nuse std::fs::File;\n\nfn main() {\n // Code here\n}";
let fix = generator.generate_fix(
&DecrustError::Style {
message: "unnecessary braces around single import".to_string(),
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Remove unnecessary braces"));
assert!(fix.commands_to_apply.len() > 0);
println!("Actual diff suggestion: {:?}", fix.diff_suggestion);
assert!(fix
.diff_suggestion
.as_ref()
.unwrap()
.contains("-use std::time::{Duration};"));
assert!(fix
.diff_suggestion
.as_ref()
.unwrap()
.contains("+use std::time::Duration;"));
}
#[test]
fn test_borrow_after_move_fix_generator() {
let generator = BorrowAfterMoveFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("message", "value used here after move: `data`");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "variables".to_string(),
message: "value used here after move: `data`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(fix.description.contains("Fix use of moved value"));
assert!(fix.description.contains("data"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(suggested_code_snippet.contains("&data"));
assert!(suggested_code_snippet.contains("data.clone()"));
assert!(suggested_code_snippet.contains("Copy trait"));
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_missing_semicolon_fix_generator() {
let generator = MissingSemicolonFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("message", "expected `;`");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "2"); params.set_confidence(0.9);
let source_context = "fn main() {\n let x = 42\n println!(\"Hello, world!\");\n}";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "syntax".to_string(),
message: "expected `;`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Add missing semicolon"));
assert!(fix.commands_to_apply.len() > 0);
if let Some(diff) = fix.diff_suggestion.as_ref() {
println!("Diff suggestion: {}", diff);
assert!(diff.contains("let x = 42") && diff.contains(";"));
}
let mut params = ExtractedParameters::default();
params.add_parameter("message", "missing semicolon after statement");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.9);
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "syntax".to_string(),
message: "missing semicolon after statement".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Add missing semicolon"));
if let Some(diff) = fix.diff_suggestion.as_ref() {
println!("Simple fix diff: {}", diff);
assert!(diff.contains("(line without semicolon)"));
assert!(diff.contains("(same line with semicolon added)"));
}
}
#[test]
fn test_mismatched_type_fix_generator() {
let generator = MismatchedTypeFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter(
"message",
"mismatched types: expected `String`, found `i32`",
);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "types".to_string(),
message: "mismatched types: expected `String`, found `i32`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(fix.description.contains("Fix type mismatch between"));
assert!(fix.description.contains("String"));
assert!(fix.description.contains("i32"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
explanation,
..
} = details
{
println!("Suggested code snippet: {}", suggested_code_snippet);
println!("Explanation: {}", explanation);
let has_conversion_methods = explanation.contains("to_string()")
|| explanation.contains("String::from")
|| suggested_code_snippet.contains("to_string()")
|| suggested_code_snippet.contains("String::from");
assert!(
has_conversion_methods,
"Expected conversion methods in suggestions"
);
} else {
panic!("Expected SuggestCodeChange details");
}
let mut params = ExtractedParameters::default();
params.add_parameter(
"message",
"mismatched types: expected `&str`, found `String`",
);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "types".to_string(),
message: "mismatched types: expected `&str`, found `String`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some());
let fix = fix.unwrap();
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
explanation,
..
} = details
{
println!("Suggested code snippet: {}", suggested_code_snippet);
println!("Explanation: {}", explanation);
let has_reference_conversion = explanation.contains("&")
|| explanation.contains("as_str()")
|| suggested_code_snippet.contains("&")
|| suggested_code_snippet.contains("as_str()");
assert!(
has_reference_conversion,
"Expected reference conversion in suggestions"
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_immutable_borrow_fix_generator() {
let generator = ImmutableBorrowFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter(
"message",
"cannot borrow `value` as mutable, as it is not declared as mutable",
);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let source_context = "fn main() {\n let value = 42;\n let ref_value = &mut value; // Error: cannot borrow as mutable\n}";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "borrowing".to_string(),
message: "cannot borrow `value` as mutable, as it is not declared as mutable"
.to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Change variable"));
assert!(fix.description.contains("value"));
assert!(fix.description.contains("mutable"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(suggested_code_snippet.contains("let mut value"));
} else {
panic!("Expected SuggestCodeChange details");
}
let mut params = ExtractedParameters::default();
params.add_parameter(
"message",
"cannot borrow `param` as mutable, as it is not declared as mutable",
);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let source_context = "fn process(param: i32) {\n let ref_param = &mut param; // Error: cannot borrow as mutable\n}";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "borrowing".to_string(),
message: "cannot borrow `param` as mutable, as it is not declared as mutable"
.to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(suggested_code_snippet.contains("mut param"));
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_missing_lifetime_fix_generator() {
let generator = MissingLifetimeFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("message", "missing lifetime specifier");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let source_context = "struct Example { field: &str }";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "lifetime".to_string(),
message: "missing lifetime specifier".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Add missing lifetime parameter"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(
suggested_code_snippet.contains("<'a>")
|| suggested_code_snippet.contains("lifetime")
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_match_pattern_fix_generator() {
let generator = MatchPatternFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("message", "non-exhaustive patterns: `None` not covered");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let context = "
match option {
Some(value) => {
println!(\"{}\", value);
}
}";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "match".to_string(),
message: "non-exhaustive patterns: `None` not covered".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Add missing patterns"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(
suggested_code_snippet.contains("_") || suggested_code_snippet.contains("None")
);
} else {
panic!("Expected SuggestCodeChange details");
}
let mut params = ExtractedParameters::default();
params.add_parameter("message", "unreachable pattern");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "match".to_string(),
message: "unreachable pattern".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert!(fix.description.contains("unreachable pattern"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange { explanation, .. } = details {
assert!(explanation.contains("unreachable") || explanation.contains("already covered"));
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_private_field_access_fix_generator() {
let generator = PrivateFieldAccessFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("message", "field `name` of struct `Person` is private");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "access".to_string(),
message: "field `name` of struct `Person` is private".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Fix access to private field"));
assert!(fix.description.contains("name"));
assert!(fix.description.contains("Person"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(
suggested_code_snippet.contains("pub name")
|| suggested_code_snippet.contains("pub fn name")
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_generic_param_conflict_fix_generator() {
let generator = GenericParamConflictFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("message", "generic parameter `T` shadows another parameter");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let source_context = "fn process<T, T>(value: T) -> T { value }";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "generics".to_string(),
message: "generic parameter `T` shadows another parameter".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Rename generic parameter"));
assert!(fix.description.contains("T"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(suggested_code_snippet.contains("T2") || suggested_code_snippet.contains("U"));
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_missing_return_fix_generator() {
let generator = MissingReturnFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("message", "expected `i32`, found `()`");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let source_context = "fn get_value() -> i32 {\n let x = 42;\n // Missing return\n}";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "return".to_string(),
message: "expected `i32`, found `()`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Add missing return value"));
assert!(fix.description.contains("i32"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(suggested_code_snippet.contains("return"));
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_enum_parameter_match_fix_generator() {
let generator = EnumParameterMatchFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter(
"message",
"expected 2 parameters, found 1 in `MyEnum::Variant`",
);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let source_context = "let value = MyEnum::Variant(42);";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "enum_parameters".to_string(),
message: "expected 2 parameters, found 1 in `MyEnum::Variant`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(fix.description.contains("Fix parameter mismatch"));
assert!(fix.description.contains("MyEnum::Variant"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
explanation,
..
} = details
{
println!("Explanation: {}", explanation);
assert!(explanation.contains("Expected 2 parameters"));
assert!(explanation.contains("found 1")); assert!(suggested_code_snippet.contains(",")); } else {
panic!("Expected SuggestCodeChange details");
}
let mut params = ExtractedParameters::default();
params.add_parameter(
"message",
"this enum variant takes 3 parameters but 2 parameters were supplied",
);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let fix = generator.generate_fix(
&DecrustError::Validation { field: "enum_parameters".to_string(), message: "this enum variant takes 3 parameters but 2 parameters were supplied for `Result::Err`".to_string(), expected: None, actual: None, rule: None, backtrace: Backtrace::generate(), },
¶ms,
None
);
assert!(fix.is_some());
let fix = fix.unwrap();
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(suggested_code_snippet.contains("enum"));
assert!(suggested_code_snippet.contains("// Check the original enum definition"));
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_struct_parameter_match_fix_generator() {
let generator = StructParameterMatchFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter("message", "missing field `name` in struct `Person`");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let source_context = "let person = Person {\n age: 30,\n // missing name field\n};";
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "struct_fields".to_string(),
message: "missing field `name` in struct `Person`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(source_context),
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(fix.description.contains("Fix field mismatch"));
assert!(fix.description.contains("Person"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
explanation,
..
} = details
{
println!("Explanation: {}", explanation);
assert!(explanation.contains("missing field"));
assert!(suggested_code_snippet.contains("name"));
} else {
panic!("Expected SuggestCodeChange details");
}
let mut params = ExtractedParameters::default();
params.add_parameter("message", "unknown field `email` in struct `Person`");
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "struct_fields".to_string(),
message: "unknown field `email` in struct `Person`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some());
let fix = fix.unwrap();
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
explanation,
..
} = details
{
assert!(explanation.contains("struct"));
assert!(explanation.contains("Person"));
assert!(suggested_code_snippet.contains("// Check the original struct definition"));
} else {
panic!("Expected SuggestCodeChange details");
}
let mut params = ExtractedParameters::default();
params.add_parameter(
"message",
"mismatched types: expected `String`, found `i32` for field `name` in struct `Person`",
);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.8);
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "struct_fields".to_string(),
message: "mismatched types: expected `String`, found `i32` for field `name` in struct `Person`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
None
);
assert!(fix.is_some());
let fix = fix.unwrap();
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(suggested_code_snippet.contains("String"));
assert!(suggested_code_snippet.contains("name"));
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_trait_implementation_fix_generators() {
let generator = MissingTraitImplFixGenerator::new();
let mut params = ExtractedParameters::default();
params.add_parameter(
"message",
"the trait `std::fmt::Display` is not implemented for `MyType`",
);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.7);
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "traits".to_string(),
message: "the trait `std::fmt::Display` is not implemented for `MyType`"
.to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(fix.description.contains("Add implementation of trait"));
assert!(fix.description.contains("Display"));
assert!(fix.description.contains("MyType"));
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(suggested_code_snippet.contains("impl std::fmt::Display for MyType"));
assert!(
suggested_code_snippet.contains("fn fmt(&self, f: &mut std::fmt::Formatter<'_>)")
);
assert!(suggested_code_snippet.contains("write!(f,"));
} else {
panic!("Expected SuggestCodeChange details");
}
let mut params = ExtractedParameters::default();
params.add_parameter(
"message",
"the trait `Clone` is not implemented for `MyType`",
);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
params.set_confidence(0.7);
let fix = generator.generate_fix(
&DecrustError::Validation {
field: "traits".to_string(),
message: "the trait `Clone` is not implemented for `MyType`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some());
let fix = fix.unwrap();
let details = fix.details.unwrap();
if let FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
} = details
{
assert!(suggested_code_snippet.contains("impl Clone for MyType"));
assert!(suggested_code_snippet.contains("fn clone(&self) -> Self"));
assert!(suggested_code_snippet.contains("#[derive(Clone)]"));
} else {
panic!("Expected SuggestCodeChange details");
}
let ast_generator = AstTraitImplementationFixGenerator::new();
let error = DecrustError::Validation {
field: "trait implementation".to_string(),
message: "the trait `std::fmt::Display` is not implemented for `MyType`".to_string(),
expected: None,
actual: None,
rule: None,
backtrace: Backtrace::generate(),
};
let mut ast_params = ExtractedParameters::new();
ast_params
.values
.insert("file_path".to_string(), "src/main.rs".to_string());
ast_params
.values
.insert("line".to_string(), "42".to_string());
let ast_fix = ast_generator.generate_fix(&error, &ast_params, None);
assert!(ast_fix.is_some());
let ast_fix = ast_fix.unwrap();
assert_eq!(ast_fix.fix_type, FixType::TextReplacement);
assert!(ast_fix.description.contains("Display"));
assert!(ast_fix.description.contains("MyType"));
assert!(ast_fix
.diff_suggestion
.unwrap()
.contains("impl std::fmt::Display for MyType"));
}
#[test]
fn test_ast_missing_import_fix_generator() {
let generator = AstMissingImportFixGenerator::new();
let error = DecrustError::Style {
message: "cannot find type `HashMap` in this scope".to_string(),
backtrace: Backtrace::generate(),
};
let mut params = ExtractedParameters::new();
params
.values
.insert("file_path".to_string(), "src/main.rs".to_string());
params.values.insert("line".to_string(), "42".to_string());
let fix = generator.generate_fix(&error, ¶ms, None);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::AddImport);
assert!(fix.description.contains("HashMap"));
assert!(fix
.diff_suggestion
.unwrap()
.contains("use std::collections::HashMap"));
}
#[test]
fn test_ast_unused_code_fix_generator() {
let generator = AstUnusedCodeFixGenerator::new();
let error = DecrustError::Style {
message: "unused variable: `foo`".to_string(),
backtrace: Backtrace::generate(),
};
let mut params = ExtractedParameters::new();
params
.values
.insert("file_path".to_string(), "src/main.rs".to_string());
params.values.insert("line".to_string(), "42".to_string());
let fix = generator.generate_fix(&error, ¶ms, None);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix
.description
.contains("Add underscore to unused variable"));
assert!(fix.description.contains("foo"));
assert!(fix.diff_suggestion.unwrap().contains("_foo"));
}
#[test]
fn test_io_missing_directory_fix_generator() {
let generator = IoMissingDirectoryFixGenerator::new();
let error = DecrustError::Io {
source: std::io::Error::new(std::io::ErrorKind::NotFound, "No such file or directory"),
path: Some(PathBuf::from("/path/to/missing/dir")),
operation: "open".to_string(),
backtrace: Backtrace::generate(),
};
let mut params = ExtractedParameters::new();
params
.values
.insert("path".to_string(), "/path/to/missing/dir".to_string());
params.values.insert(
"message".to_string(),
"No such file or directory".to_string(),
);
let fix = generator.generate_fix(&error, ¶ms, None);
assert!(fix.is_some());
let fix = fix.unwrap();
assert!(matches!(
fix.fix_type,
FixType::ExecuteCommand | FixType::TextReplacement
));
assert!(fix.description.contains("Create missing directory"));
assert!(fix.description.contains("/path/to/missing/dir"));
if let Some(FixDetails::SuggestCommand { command, .. }) = fix.details {
assert!(command.contains("mkdir -p /path/to/missing/dir"));
} else {
panic!("Expected SuggestCommand details");
}
assert_eq!(fix.commands_to_apply.len(), 1);
assert!(fix.commands_to_apply[0].contains("mkdir -p /path/to/missing/dir"));
}
#[test]
fn test_io_permission_fix_generator() {
let generator = IoPermissionFixGenerator::new();
let error = DecrustError::Io {
source: std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Permission denied"),
path: Some(PathBuf::from("/path/to/file.txt")),
operation: "open".to_string(),
backtrace: Backtrace::generate(),
};
let mut params = ExtractedParameters::new();
params
.values
.insert("path".to_string(), "/path/to/file.txt".to_string());
params
.values
.insert("message".to_string(), "Permission denied".to_string());
let fix = generator.generate_fix(&error, ¶ms, None);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ExecuteCommand);
assert!(fix.description.contains("Fix permissions for"));
assert!(fix.description.contains("/path/to/file.txt"));
if let Some(FixDetails::SuggestCommand { command, .. }) = fix.details {
assert!(command.contains("chmod 644 /path/to/file.txt"));
} else {
panic!("Expected SuggestCommand details");
}
assert_eq!(fix.commands_to_apply.len(), 1);
assert!(fix.commands_to_apply[0].contains("chmod 644 /path/to/file.txt"));
let error = DecrustError::Io {
source: std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Permission denied"),
path: Some(PathBuf::from("/path/to/directory")),
operation: "open".to_string(),
backtrace: Backtrace::generate(),
};
let mut params = ExtractedParameters::new();
params
.values
.insert("path".to_string(), "/path/to/directory".to_string());
params
.values
.insert("message".to_string(), "Permission denied".to_string());
let fix = generator.generate_fix(&error, ¶ms, None);
assert!(fix.is_some());
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ExecuteCommand);
assert!(fix.description.contains("Fix permissions for"));
assert!(fix.description.contains("/path/to/directory"));
if let Some(FixDetails::SuggestCommand { command, .. }) = fix.details {
assert!(command.contains("chmod 755 /path/to/directory"));
} else {
panic!("Expected SuggestCommand details");
}
assert_eq!(fix.commands_to_apply.len(), 1);
assert!(fix.commands_to_apply[0].contains("chmod 755 /path/to/directory"));
}
#[test]
fn test_return_local_reference_fix_generator() {
let generator = ReturnLocalReferenceFixGenerator::new();
let params = {
let mut p = ExtractedParameters::new();
p.add_parameter(
"message",
"error[E0515]: cannot return reference to local variable `value`",
);
p.add_parameter("file_path", "src/main.rs");
p.add_parameter("line", "10");
p
};
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "Ownership error".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
¶ms,
Some(
"fn get_value() -> &str {\n let value = String::from(\"hello\");\n &value\n}",
),
);
assert!(
fix.is_some(),
"Fix should be generated for return local reference error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("reference to local variable"),
"Description should mention reference to local variable"
);
if let Some(FixDetails::SuggestCodeChange { explanation, .. }) = &fix.details {
assert!(
explanation.contains("E0515"),
"Explanation should mention E0515"
);
assert!(
explanation.contains("owned value"),
"Explanation should suggest returning an owned value"
);
assert!(
explanation.contains("lifetime"),
"Explanation should mention lifetimes"
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_unstable_feature_fix_generator() {
let generator = UnstableFeatureFixGenerator::new();
let params = {
let mut p = ExtractedParameters::new();
p.add_parameter(
"message",
"error[E0658]: use of unstable feature 'try_trait'",
);
p.add_parameter("file_path", "src/main.rs");
p.add_parameter("line", "15");
p
};
let fix = generator.generate_fix(
&DecrustError::Internal { message: "Unstable feature".to_string(), source: OptionalError::new(None), component: None, backtrace: Backtrace::generate(), },
¶ms,
Some("fn example() {\n let result: Option<i32> = Some(5);\n let value = result?; // Using try_trait\n}")
);
assert!(
fix.is_some(),
"Fix should be generated for unstable feature error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("unstable feature"),
"Description should mention unstable feature"
);
assert!(
!fix.commands_to_apply.is_empty(),
"Commands should be generated"
);
assert!(
fix.commands_to_apply
.iter()
.any(|cmd| cmd.contains("rustup")),
"Commands should include rustup"
);
if let Some(FixDetails::SuggestCodeChange { explanation, .. }) = &fix.details {
assert!(
explanation.contains("E0658"),
"Explanation should mention E0658"
);
assert!(
explanation.contains("nightly"),
"Explanation should mention nightly"
);
assert!(
explanation.contains("feature"),
"Explanation should mention feature flag"
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_invalid_argument_count_fix_generator() {
let generator = InvalidArgumentCountFixGenerator::new();
let params = {
let mut p = ExtractedParameters::new();
p.add_parameter(
"message",
"error[E0061]: this function takes 3 parameters but 1 parameter was supplied",
);
p.add_parameter("file_path", "src/main.rs");
p.add_parameter("line", "20");
p
};
let fix = generator.generate_fix(
&DecrustError::Internal { message: "Invalid arguments".to_string(), source: OptionalError::new(None), component: None, backtrace: Backtrace::generate(), },
¶ms,
Some("fn example() {\n process_data(42); // Should be process_data(42, \"text\", true)\n}")
);
assert!(
fix.is_some(),
"Fix should be generated for invalid argument count error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("incorrect number of arguments"),
"Description should mention incorrect number of arguments"
);
if let Some(FixDetails::SuggestCodeChange { explanation, .. }) = &fix.details {
assert!(
explanation.contains("E0061"),
"Explanation should mention E0061"
);
assert!(
explanation.contains("3"),
"Explanation should mention expected count"
);
assert!(
explanation.contains("1"),
"Explanation should mention actual count"
);
} else {
panic!("Expected SuggestCodeChange details");
}
let source_code =
"fn example() {\n print_value(42, \"text\", true); // Should be print_value(42)\n}";
let fix = Autocorrection {
description: "Fix function call with incorrect number of arguments (E0061)".to_string(),
fix_type: FixType::ManualInterventionRequired,
confidence: 0.9,
details: Some(FixDetails::SuggestCodeChange {
file_path: PathBuf::from("src/main.rs"),
line_hint: 25,
suggested_code_snippet: source_code.to_string(),
explanation: "Error E0061: This function call has an incorrect number of arguments.\n\n\
The function expects 1 arguments, but you provided 3. You need to remove 2 extra argument(s).\n\n\
Consider these solutions:\n\
// 1. Remove the extra arguments\n\
// 2. Check the function signature".to_string(),
}),
diff_suggestion: None,
commands_to_apply: vec![],
targets_error_code: Some("E0061".to_string()),
};
if let Some(FixDetails::SuggestCodeChange { explanation, .. }) = &fix.details {
assert!(
explanation.contains("1"),
"Explanation should mention expected count"
);
assert!(
explanation.contains("3"),
"Explanation should mention actual count"
);
assert!(
explanation.contains("remove"),
"Explanation should suggest removing arguments"
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_unsafe_unwrap_fix_generator() {
let generator: Box<dyn FixGenerator> = Box::new(UnsafeUnwrapFixGenerator::new());
let code = "fn get_value() -> String {\n let result: Result<String, Error> = process();\n result.unwrap()\n}";
let error = DecrustError::Internal {
message: "Runtime error".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
};
let params = ExtractedParameters::new();
let fix = generator.generate_fix(&error, ¶ms, Some(code));
assert!(fix.is_some(), "Fix should be generated for unsafe unwrap");
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(
fix.description.contains("unwrap"),
"Description should mention unwrap"
);
if let Some(FixDetails::SuggestCodeChange {
explanation,
suggested_code_snippet,
..
}) = &fix.details
{
assert!(
explanation.contains("unwrap"),
"Explanation should mention unwrap"
);
assert!(
explanation.contains("panic"),
"Explanation should mention panic"
);
assert!(
suggested_code_snippet.contains("match"),
"Suggested code should use match"
);
assert!(
suggested_code_snippet.contains("Ok(value)"),
"Suggested code should handle Ok case"
);
assert!(
suggested_code_snippet.contains("Err"),
"Suggested code should handle Err case"
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_missing_ok_err_fix_generator() {
let generator: Box<dyn FixGenerator> = Box::new(MissingOkErrFixGenerator::new());
let code = "fn process_result(result: Result<String, Error>) {\n match result {\n Ok(value) => {\n println!(\"Success: {}\", value);\n }\n // Missing Err arm\n }\n}";
let error = DecrustError::Internal {
message: "Incomplete match".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
};
let params = ExtractedParameters::new();
let fix = generator.generate_fix(&error, ¶ms, Some(code));
assert!(
fix.is_some(),
"Fix should be generated for incomplete match"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(
fix.description.contains("match"),
"Description should mention match"
);
if let Some(FixDetails::SuggestCodeChange {
explanation,
suggested_code_snippet,
..
}) = &fix.details
{
assert!(
explanation.contains("Result"),
"Explanation should mention Result"
);
assert!(
explanation.contains("variants"),
"Explanation should mention variants"
);
assert!(
suggested_code_snippet.contains("Err("),
"Suggested code should include Err arm"
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_division_by_zero_fix_generator() {
let generator: Box<dyn FixGenerator> = Box::new(DivisionByZeroFixGenerator::new());
let code = "fn divide(a: i32) -> i32 {\n a / 0 // Explicit division by zero\n}";
let error = DecrustError::Internal {
message: "Division by zero".to_string(),
source: OptionalError::new(None),
component: Some("division_checker".to_string()),
backtrace: Backtrace::generate(),
};
let params = ExtractedParameters::new();
let fix = generator.generate_fix(&error, ¶ms, Some(code));
assert!(
fix.is_some(),
"Fix should be generated for division by zero"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(
fix.description.contains("division"),
"Description should mention division"
);
if let Some(FixDetails::SuggestCodeChange {
suggested_code_snippet,
explanation,
..
}) = &fix.details
{
assert!(
explanation.contains("zero") || explanation.contains("division"),
"Explanation should mention zero or division"
);
assert!(
suggested_code_snippet.contains("if") || suggested_code_snippet.contains("check"),
"Suggested code should contain conditional check"
);
}
}
#[test]
fn test_closure_capture_and_recursive_type_fix_generators() {
let closure_generator: Box<dyn FixGenerator> =
Box::new(ClosureCaptureLifetimeFixGenerator::new());
let closure_message = "error[E0373]: closure may outlive the current function, but it borrows `data`, which is owned by the current function";
let mut params = ExtractedParameters::new();
params.add_parameter("message", closure_message);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "10");
let error = DecrustError::Internal {
message: "Closure capture error".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
};
let source_code = "fn process_data(data: Vec<String>) {\n let closure = || {\n println!(\"{:?}\", data);\n };\n std::thread::spawn(closure);\n}";
let fix = closure_generator.generate_fix(&error, ¶ms, Some(source_code));
assert!(
fix.is_some(),
"Fix should be generated for closure capture error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("closure capture"),
"Description should mention closure capture"
);
if let Some(FixDetails::SuggestCodeChange {
explanation,
suggested_code_snippet,
..
}) = &fix.details
{
assert!(
explanation.contains("E0373"),
"Explanation should mention E0373"
);
assert!(
explanation.contains("outlive"),
"Explanation should mention outlive"
);
assert!(
suggested_code_snippet.contains("move ||"),
"Suggested code should include move closure"
);
assert!(
suggested_code_snippet.contains("clone"),
"Suggested code should mention cloning"
);
assert!(
suggested_code_snippet.contains("Arc"),
"Suggested code should mention Arc"
);
} else {
panic!("Expected SuggestCodeChange details");
}
let recursive_generator: Box<dyn FixGenerator> = Box::new(RecursiveTypeFixGenerator::new());
let recursive_message = "error[E0072]: recursive type `LinkedList` has infinite size";
let mut params = ExtractedParameters::new();
params.add_parameter("message", recursive_message);
params.add_parameter("file_path", "src/main.rs");
params.add_parameter("line", "15");
let error = DecrustError::Internal {
message: "Recursive type error".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
};
let source_code = "struct LinkedList {\n data: i32,\n next: Option<LinkedList>, // Recursive without indirection\n}";
let fix = recursive_generator.generate_fix(&error, ¶ms, Some(source_code));
assert!(
fix.is_some(),
"Fix should be generated for recursive type error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("recursive type"),
"Description should mention recursive type"
);
if let Some(FixDetails::SuggestCodeChange {
explanation,
suggested_code_snippet,
..
}) = &fix.details
{
assert!(
explanation.contains("E0072"),
"Explanation should mention E0072"
);
assert!(
explanation.contains("infinite size"),
"Explanation should mention infinite size"
);
assert!(
suggested_code_snippet.contains("Box<"),
"Suggested code should include Box"
);
assert!(
suggested_code_snippet.contains("Rc<"),
"Suggested code should mention Rc"
);
assert!(
suggested_code_snippet.contains("RefCell"),
"Suggested code should mention RefCell"
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_runtime_panic_fix_generator() {
let generator: Box<dyn FixGenerator> = Box::new(RuntimePanicFixGenerator::new());
let code = "fn process(value: Option<String>) -> String {\n if value.is_none() {\n panic!(\"Value is None\");\n }\n value.unwrap()\n}";
let error = DecrustError::Internal {
message: "Runtime panic".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
};
let params = ExtractedParameters::new();
let fix = generator.generate_fix(&error, ¶ms, Some(code));
assert!(fix.is_some(), "Fix should be generated for runtime panic");
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(
fix.description.contains("panic"),
"Description should mention panic"
);
if let Some(FixDetails::SuggestCodeChange {
explanation,
suggested_code_snippet,
..
}) = &fix.details
{
assert!(
explanation.contains("panic"),
"Explanation should mention panic"
);
assert!(
explanation.contains("Result"),
"Explanation should mention Result"
);
assert!(
suggested_code_snippet.contains("Err"),
"Suggested code should include Err"
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_question_mark_propagation_fix_generator() {
let generator: Box<dyn FixGenerator> = Box::new(QuestionMarkPropagationFixGenerator::new());
let code = "fn process_file(path: &str) {\n let file = File::open(path)?;\n let content = read_to_string(file)?;\n println!(\"{}\", content);\n}";
let error = DecrustError::Internal {
message: "Question mark error".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
};
let params = ExtractedParameters::new();
let fix = generator.generate_fix(&error, ¶ms, Some(code));
assert!(
fix.is_some(),
"Fix should be generated for question mark without Result return"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(
fix.description.contains("question mark"),
"Description should mention question mark"
);
if let Some(FixDetails::SuggestCodeChange {
explanation,
suggested_code_snippet,
..
}) = &fix.details
{
assert!(explanation.contains("?"), "Explanation should mention ?");
assert!(
explanation.contains("Result"),
"Explanation should mention Result"
);
assert!(
suggested_code_snippet.contains("-> Result"),
"Suggested code should add Result return type"
);
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_network_tls_fix_generator() {
let generator = NetworkTlsFixGenerator::new();
let error = DecrustError::Network {
source: Box::new(std::io::Error::other("SSL certificate verification failed")),
url: Some("https://api.example.com".to_string()),
kind: "TLS".to_string(),
backtrace: Backtrace::generate(),
};
let fix = generator.generate_fix(&error, &ExtractedParameters::new(), None);
assert!(
fix.is_some(),
"Fix should be generated for TLS certificate error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("TLS") || fix.description.contains("certificate"),
"Description should mention TLS or certificate"
);
assert!(
!fix.commands_to_apply.is_empty(),
"Commands should be generated"
);
let params = {
let mut p = ExtractedParameters::new();
p.add_parameter(
"message",
"SSL error: self-signed certificate for api.example.com",
);
p
};
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "SSL error".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(
fix.is_some(),
"Fix should be generated for self-signed certificate error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("certificate") || fix.description.contains("trusted"),
"Description should mention certificate or trusted"
);
assert!(
!fix.commands_to_apply.is_empty(),
"Commands should be generated"
);
let params = {
let mut p = ExtractedParameters::new();
p.add_parameter("message", "TLS certificate hostname mismatch: expected 'api.example.com', got 'www.example.com'");
p
};
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "TLS error".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(
fix.is_some(),
"Fix should be generated for hostname mismatch error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("hostname") || fix.description.contains("mismatch"),
"Description should mention hostname or mismatch"
);
assert!(
!fix.commands_to_apply.is_empty(),
"Commands should be generated"
);
}
#[test]
fn test_network_connection_fix_generator() {
let generator = NetworkConnectionFixGenerator::new();
let error = DecrustError::Network {
source: Box::new(std::io::Error::new(
std::io::ErrorKind::ConnectionRefused,
"Connection refused",
)),
url: Some("https://api.example.com:8080".to_string()),
kind: "HTTP".to_string(),
backtrace: Backtrace::generate(),
};
let fix = generator.generate_fix(&error, &ExtractedParameters::new(), None);
assert!(
fix.is_some(),
"Fix should be generated for connection refused error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("Check"),
"Description should suggest checking something"
);
assert!(
!fix.commands_to_apply.is_empty(),
"Commands should be generated"
);
let params = {
let mut p = ExtractedParameters::new();
p.add_parameter("message", "Could not resolve host: api.example.com");
p
};
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "DNS error".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some(), "Fix should be generated for DNS error");
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("DNS") || fix.description.contains("host"),
"Description should mention DNS or host resolution"
);
assert!(
!fix.commands_to_apply.is_empty(),
"Commands should be generated"
);
let params = {
let mut p = ExtractedParameters::new();
p.add_parameter("message", "Connection timed out to server.example.com:443");
p
};
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "Timeout error".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
¶ms,
None,
);
assert!(fix.is_some(), "Fix should be generated for timeout error");
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ManualInterventionRequired);
assert!(
fix.description.contains("network") || fix.description.contains("connectivity"),
"Description should mention network or connectivity"
);
assert!(
!fix.commands_to_apply.is_empty(),
"Commands should be generated"
);
}
#[test]
fn test_unused_mut_fix_generator() {
let generator = UnusedMutFixGenerator::new();
let code = "let mut counter = 0;\nprintln!(\"Counter: {}\", counter);";
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "Style warning".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
&ExtractedParameters::new(),
Some(code),
);
assert!(fix.is_some(), "Fix should be generated for unused mut");
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Remove unused 'mut' keyword"));
if let Some(FixDetails::SuggestCodeChange {
suggested_code_snippet,
explanation,
..
}) = &fix.details
{
assert!(suggested_code_snippet.contains("let counter = 0"));
assert!(!suggested_code_snippet.contains("mut"));
assert!(explanation.contains("never mutated"));
} else {
panic!("Expected SuggestCodeChange details");
}
let code = "let mut counter = 0;\ncounter += 1;\nprintln!(\"Counter: {}\", counter);";
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "Style warning".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
&ExtractedParameters::new(),
Some(code),
);
assert!(
fix.is_none(),
"No fix should be generated for a variable that is mutated"
);
let code = "let mut data = vec![1, 2, 3];\nprocess_data(&mut data);\nprintln!(\"Data: {:?}\", data);";
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "Style warning".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
&ExtractedParameters::new(),
Some(code),
);
assert!(
fix.is_none(),
"No fix should be generated for a variable that is used with &mut"
);
}
#[test]
fn test_unnecessary_parentheses_fix_generator() {
let generator = UnnecessaryParenthesesFixGenerator::new();
let code =
"use std::time::{Duration};\n\nfn main() {\n let d = Duration::from_secs(1);\n}";
let error = DecrustError::Style {
message: "Unnecessary braces in import statement".to_string(),
backtrace: Backtrace::generate(),
};
let mut params = ExtractedParameters::new();
params
.values
.insert("file_path".to_string(), "src/main.rs".to_string());
params.values.insert("line".to_string(), "1".to_string());
let fix = generator.generate_fix(&error, ¶ms, Some(code));
assert!(
fix.is_some(),
"Fix should be generated for unnecessary parentheses"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(
fix.description.contains("Remove unnecessary parentheses"),
"Description should mention removing unnecessary parentheses"
);
if let Some(FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
}) = &fix.details
{
assert_eq!(
suggested_code_snippet, "use std::time::Duration;",
"Fix should remove the unnecessary braces"
);
} else {
panic!("Expected SuggestCodeChange details");
}
assert!(fix.diff_suggestion.is_some());
let diff = fix.diff_suggestion.unwrap();
assert!(diff.contains("- use std::time::{Duration};"));
assert!(diff.contains("+ use std::time::Duration;"));
}
#[test]
fn test_unnecessary_clone_fix_generator() {
let generator = UnnecessaryCloneFixGenerator::new();
let code = "let result = items.iter().map(move |item| item.clone()).collect::<Vec<_>>();";
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "Style warning".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
&ExtractedParameters::new(),
Some(code),
);
assert!(
fix.is_some(),
"Fix should be generated for unnecessary clone in closure"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Remove unnecessary clone() call"));
if let Some(FixDetails::SuggestCodeChange {
suggested_code_snippet,
explanation,
..
}) = &fix.details
{
assert!(suggested_code_snippet.contains("item"));
assert!(!suggested_code_snippet.contains("clone()"));
assert!(explanation.contains("unnecessary"));
} else {
panic!("Expected SuggestCodeChange details");
}
let code = "let ref_value = &value.clone();";
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "Style warning".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
&ExtractedParameters::new(),
Some(code),
);
assert!(
fix.is_some(),
"Fix should be generated for reference to a clone"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Remove unnecessary clone() call"));
if let Some(FixDetails::SuggestCodeChange {
suggested_code_snippet,
explanation,
..
}) = &fix.details
{
assert!(suggested_code_snippet.contains("value"));
assert!(!suggested_code_snippet.contains("clone()"));
assert!(explanation.contains("reference"));
} else {
panic!("Expected SuggestCodeChange details");
}
let code = "process_data(data.clone());";
let fix = generator.generate_fix(
&DecrustError::Internal {
message: "Style warning".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
&ExtractedParameters::new(),
Some(code),
);
assert!(
fix.is_some(),
"Fix should be generated for clone in function call"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Remove unnecessary clone() call"));
if let Some(FixDetails::SuggestCodeChange {
suggested_code_snippet,
explanation,
..
}) = &fix.details
{
assert!(suggested_code_snippet.contains("&data"));
assert!(!suggested_code_snippet.contains("clone()"));
assert!(explanation.contains("reference"));
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_parse_error_fix_generators() {
let yaml_generator = YamlParseFixGenerator::new();
let yaml_error = DecrustError::Parse {
source: Box::new(std::io::Error::new(std::io::ErrorKind::InvalidData, "YAML syntax error")),
kind: "YAML".to_string(),
context_info: "YAML syntax error at line 5, column 3: mapping values are not allowed in this context".to_string(),
backtrace: Backtrace::generate(),
};
let mut yaml_params = ExtractedParameters::new();
yaml_params
.values
.insert("file_path".to_string(), "/path/to/config.yaml".to_string());
yaml_params.values.insert(
"message".to_string(),
"YAML syntax error at line 5, column 3: mapping values are not allowed in this context"
.to_string(),
);
let yaml_fix = yaml_generator.generate_fix(&yaml_error, &yaml_params, None);
assert!(
yaml_fix.is_some(),
"Fix should be generated for YAML parse error"
);
let yaml_fix = yaml_fix.unwrap();
assert_eq!(yaml_fix.fix_type, FixType::ExecuteCommand);
assert!(yaml_fix.description.contains("Fix YAML parsing error"));
if let Some(FixDetails::SuggestCommand {
command,
explanation,
}) = &yaml_fix.details
{
assert!(command.contains("yamllint -f parsable"));
assert!(explanation.contains("line 5"));
assert!(explanation.contains("column 3"));
assert!(explanation.contains("mapping values are not allowed in this context"));
assert!(explanation.contains("Check your indentation"));
} else {
panic!("Expected SuggestCommand details");
}
let mut yaml_params2 = ExtractedParameters::new();
yaml_params2
.values
.insert("file_path".to_string(), "/path/to/data.yml".to_string());
yaml_params2.values.insert(
"message".to_string(),
"Invalid YAML: found unexpected end of stream".to_string(),
);
let yaml_fix2 = yaml_generator.generate_fix(
&DecrustError::Internal {
message: "YAML parsing failed".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
&yaml_params2,
None,
);
assert!(
yaml_fix2.is_some(),
"Fix should be generated for YAML parse error with different format"
);
let yaml_fix2 = yaml_fix2.unwrap();
assert_eq!(yaml_fix2.fix_type, FixType::ExecuteCommand);
assert!(yaml_fix2.description.contains("Fix YAML parsing error"));
if let Some(FixDetails::SuggestCommand {
command,
explanation,
}) = &yaml_fix2.details
{
assert!(command.contains("yamllint -f parsable"));
assert!(explanation.contains("YAML parsing error"));
assert!(explanation.contains("found unexpected end of stream"));
assert!(explanation.contains("Check for incomplete structures"));
} else {
panic!("Expected SuggestCommand details");
}
let json_generator = JsonParseFixGenerator::new();
let json_error = DecrustError::Parse {
source: Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"JSON syntax error",
)),
kind: "JSON".to_string(),
context_info: "JSON syntax error at line 42, column 10: expected ',' but found '}'"
.to_string(),
backtrace: Backtrace::generate(),
};
let mut json_params = ExtractedParameters::new();
json_params
.values
.insert("file_path".to_string(), "/path/to/config.json".to_string());
json_params.values.insert(
"message".to_string(),
"JSON syntax error at line 42, column 10: expected ',' but found '}'".to_string(),
);
let json_fix = json_generator.generate_fix(&json_error, &json_params, None);
assert!(
json_fix.is_some(),
"Fix should be generated for JSON parse error"
);
let json_fix = json_fix.unwrap();
assert_eq!(json_fix.fix_type, FixType::ExecuteCommand);
assert!(json_fix.description.contains("Fix JSON parsing error"));
if let Some(FixDetails::SuggestCommand {
command,
explanation,
}) = &json_fix.details
{
assert!(command.contains("jsonlint --fix"));
assert!(explanation.contains("line 42"));
assert!(explanation.contains("column 10"));
} else {
panic!("Expected SuggestCommand details");
}
let mut json_params2 = ExtractedParameters::new();
json_params2
.values
.insert("file_path".to_string(), "/path/to/data.json".to_string());
json_params2.values.insert(
"message".to_string(),
"Invalid JSON: Unexpected end of input at position 100".to_string(),
);
let json_fix2 = json_generator.generate_fix(
&DecrustError::Internal {
message: "JSON parsing failed".to_string(),
source: OptionalError::new(None),
component: None,
backtrace: Backtrace::generate(),
},
&json_params2,
None,
);
assert!(
json_fix2.is_some(),
"Fix should be generated for JSON parse error with different format"
);
let json_fix2 = json_fix2.unwrap();
assert_eq!(json_fix2.fix_type, FixType::ExecuteCommand);
assert!(json_fix2.description.contains("Fix JSON parsing error"));
if let Some(FixDetails::SuggestCommand {
command,
explanation,
}) = &json_fix2.details
{
assert!(command.contains("jsonlint --fix"));
assert!(explanation.contains("JSON parsing error"));
} else {
panic!("Expected SuggestCommand details");
}
}
#[test]
fn test_config_missing_key_fix_generator() {
let generator = ConfigMissingKeyFixGenerator::new();
let error = DecrustError::Config {
message: "missing key: \"api_key\"".to_string(),
path: Some(PathBuf::from("/path/to/config.json")),
source: OptionalError::new(None),
backtrace: Backtrace::generate(),
};
let mut params = ExtractedParameters::new();
params
.values
.insert("file_path".to_string(), "/path/to/config.json".to_string());
params.values.insert(
"message".to_string(),
"missing key: \"api_key\"".to_string(),
);
let fix = generator.generate_fix(&error, ¶ms, None);
assert!(
fix.is_some(),
"Fix should be generated for missing key error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::TextReplacement);
assert!(fix.description.contains("Add missing configuration key"));
assert!(fix.description.contains("api_key"));
if let Some(FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
}) = &fix.details
{
assert!(suggested_code_snippet.contains("api_key"));
} else {
panic!("Expected SuggestCodeChange details");
}
let yaml_error = DecrustError::Config {
message: "required key not found: host".to_string(),
path: Some(PathBuf::from("/path/to/config.yaml")),
source: OptionalError::new(None),
backtrace: Backtrace::generate(),
};
let mut yaml_params = ExtractedParameters::new();
yaml_params
.values
.insert("file_path".to_string(), "/path/to/config.yaml".to_string());
yaml_params.values.insert(
"message".to_string(),
"required key not found: host".to_string(),
);
let yaml_fix = generator.generate_fix(&yaml_error, &yaml_params, None);
assert!(
yaml_fix.is_some(),
"Fix should be generated for missing key error in YAML"
);
let yaml_fix = yaml_fix.unwrap();
assert_eq!(yaml_fix.fix_type, FixType::TextReplacement);
assert!(yaml_fix
.description
.contains("Add missing configuration key"));
assert!(yaml_fix.description.contains("host"));
if let Some(FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
}) = &yaml_fix.details
{
assert!(suggested_code_snippet.contains("host"));
} else {
panic!("Expected SuggestCodeChange details");
}
let toml_error = DecrustError::Config {
message: "missing field: timeout".to_string(),
path: Some(PathBuf::from("/path/to/config.toml")),
source: OptionalError::new(None),
backtrace: Backtrace::generate(),
};
let mut toml_params = ExtractedParameters::new();
toml_params
.values
.insert("file_path".to_string(), "/path/to/config.toml".to_string());
toml_params
.values
.insert("message".to_string(), "missing field: timeout".to_string());
let toml_fix = generator.generate_fix(&toml_error, &toml_params, None);
assert!(
toml_fix.is_some(),
"Fix should be generated for missing key error in TOML"
);
let toml_fix = toml_fix.unwrap();
assert_eq!(toml_fix.fix_type, FixType::TextReplacement);
assert!(toml_fix
.description
.contains("Add missing configuration key"));
assert!(toml_fix.description.contains("timeout"));
if let Some(FixDetails::SuggestCodeChange {
suggested_code_snippet,
..
}) = &toml_fix.details
{
assert!(suggested_code_snippet.contains("timeout"));
assert!(suggested_code_snippet.contains("60"));
} else {
panic!("Expected SuggestCodeChange details");
}
}
#[test]
fn test_config_syntax_fix_generator() {
let generator = ConfigSyntaxFixGenerator::new();
let error = DecrustError::Config {
message: "JSON syntax error at line 42".to_string(),
path: Some(PathBuf::from("/path/to/config.json")),
source: OptionalError::new(None),
backtrace: Backtrace::generate(),
};
let mut params = ExtractedParameters::new();
params
.values
.insert("file_path".to_string(), "/path/to/config.json".to_string());
params.values.insert(
"message".to_string(),
"JSON syntax error at line 42".to_string(),
);
println!("Test params: {:?}", params);
let is_json =
generator.is_json_syntax_error("JSON syntax error at line 42", "/path/to/config.json");
println!("Direct is_json check: {}", is_json);
let fix = generator.generate_fix(&error, ¶ms, None);
println!("Fix result: {:?}", fix);
assert!(
fix.is_some(),
"Fix should be generated for JSON syntax error"
);
let fix = fix.unwrap();
assert_eq!(fix.fix_type, FixType::ExecuteCommand);
assert!(fix
.description
.contains("Fix syntax error in configuration file"));
assert!(fix.description.contains("/path/to/config.json"));
if let Some(FixDetails::SuggestCommand { ref command, .. }) = fix.details {
assert!(command.contains("jsonlint --fix /path/to/config.json"));
} else {
panic!("Expected SuggestCommand details");
}
assert_eq!(fix.commands_to_apply.len(), 1);
assert!(fix.commands_to_apply[0].contains("jsonlint --fix /path/to/config.json"));
let yaml_error = DecrustError::Config {
message: "YAML syntax error at line 10".to_string(),
path: Some(PathBuf::from("/path/to/config.yaml")),
source: OptionalError::new(None),
backtrace: Backtrace::generate(),
};
let mut yaml_params = ExtractedParameters::new();
yaml_params
.values
.insert("file_path".to_string(), "/path/to/config.yaml".to_string());
yaml_params.values.insert(
"message".to_string(),
"YAML syntax error at line 10".to_string(),
);
println!("YAML test params: {:?}", yaml_params);
let is_yaml =
generator.is_yaml_syntax_error("YAML syntax error at line 10", "/path/to/config.yaml");
println!("Direct is_yaml check: {}", is_yaml);
let yaml_fix = generator.generate_fix(&yaml_error, &yaml_params, None);
println!("YAML fix result: {:?}", yaml_fix);
assert!(
yaml_fix.is_some(),
"Fix should be generated for YAML syntax error"
);
let yaml_fix = yaml_fix.unwrap();
assert_eq!(yaml_fix.fix_type, FixType::ExecuteCommand);
assert!(yaml_fix
.description
.contains("Fix syntax error in configuration file"));
assert!(yaml_fix.description.contains("/path/to/config.yaml"));
if let Some(FixDetails::SuggestCommand { ref command, .. }) = yaml_fix.details {
assert!(command.contains("yamllint /path/to/config.yaml"));
} else {
panic!("Expected SuggestCommand details");
}
assert_eq!(yaml_fix.commands_to_apply.len(), 1);
assert!(yaml_fix.commands_to_apply[0].contains("yamllint /path/to/config.yaml"));
let toml_error = DecrustError::Config {
message: "TOML syntax error at line 5".to_string(),
path: Some(PathBuf::from("/path/to/config.toml")),
source: OptionalError::new(None),
backtrace: Backtrace::generate(),
};
let mut toml_params = ExtractedParameters::new();
toml_params
.values
.insert("file_path".to_string(), "/path/to/config.toml".to_string());
toml_params.values.insert(
"message".to_string(),
"TOML syntax error at line 5".to_string(),
);
println!("TOML test params: {:?}", toml_params);
let is_toml =
generator.is_toml_syntax_error("TOML syntax error at line 5", "/path/to/config.toml");
println!("Direct is_toml check: {}", is_toml);
let toml_fix = generator.generate_fix(&toml_error, &toml_params, None);
println!("TOML fix result: {:?}", toml_fix);
assert!(
toml_fix.is_some(),
"Fix should be generated for TOML syntax error"
);
let toml_fix = toml_fix.unwrap();
assert_eq!(toml_fix.fix_type, FixType::ExecuteCommand);
assert!(toml_fix
.description
.contains("Fix syntax error in configuration file"));
assert!(toml_fix.description.contains("/path/to/config.toml"));
if let Some(FixDetails::SuggestCommand { ref command, .. }) = toml_fix.details {
assert!(command.contains("taplo fmt /path/to/config.toml"));
} else {
panic!("Expected SuggestCommand details");
}
assert_eq!(toml_fix.commands_to_apply.len(), 1);
assert!(toml_fix.commands_to_apply[0].contains("taplo fmt /path/to/config.toml"));
}
}