#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
#[tokio::test]
#[serial(bug_report_error_file)]
async fn test_handle_bug_report_no_error() {
let _ = clear_error();
let result = handle_bug_report(None, true, false, false).await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("No captured error found"));
}
#[tokio::test]
#[serial(bug_report_error_file)]
#[ignore = "Requires HOME directory to be set in test environment"]
async fn test_handle_bug_report_clear() {
let result = handle_bug_report(None, false, false, true).await;
assert!(result.is_ok());
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error() {
capture_command_error("pmat", &["test".to_string()], "test error");
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_empty_args() {
capture_command_error("pmat", &[], "error with no args");
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_multiple_args() {
capture_command_error(
"pmat",
&[
"work".to_string(),
"status".to_string(),
"--verbose".to_string(),
],
"complex error",
);
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_long_error_message() {
let long_error = "A".repeat(10000);
capture_command_error("pmat", &["test".to_string()], &long_error);
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_unicode() {
capture_command_error("pmat", &["test".to_string()], "Error: 日本語 эррор 🚫");
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_code() {
capture_command_error_with_code("pmat", &["work".to_string()], "exit error", 1);
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_code_zero() {
capture_command_error_with_code("pmat", &["work".to_string()], "success but captured", 0);
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_code_negative() {
capture_command_error_with_code("pmat", &["work".to_string()], "signal error", -9);
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_code_large() {
capture_command_error_with_code("pmat", &["work".to_string()], "error", 255);
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_special_characters() {
capture_command_error(
"pmat",
&["test".to_string()],
"Error: `backticks` and **bold** and <html>tags</html>",
);
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_newlines() {
capture_command_error(
"pmat",
&["test".to_string()],
"Line 1: Error\nLine 2: Details\nLine 3: Stack trace",
);
}
#[test]
#[serial(bug_report_error_file)]
fn test_capture_command_error_with_tabs_and_whitespace() {
capture_command_error(
"pmat",
&["test".to_string()],
"Error:\t\tTabbed\n Spaces \r\nCRLF",
);
}
#[test]
fn test_issue_content_parsing_with_title() {
let content = "TITLE: My Bug Title\n---\n## Summary\n\nBody content here.";
let parts: Vec<&str> = content.splitn(2, "\n---\n").collect();
let title = parts
.first()
.unwrap_or(&"Bug report")
.strip_prefix("TITLE: ")
.unwrap_or("Bug report");
let body = parts.get(1).unwrap_or(&"");
assert_eq!(title, "My Bug Title");
assert_eq!(*body, "## Summary\n\nBody content here.");
}
#[test]
fn test_issue_content_parsing_no_separator() {
let content = "Just some content without proper format";
let parts: Vec<&str> = content.splitn(2, "\n---\n").collect();
let title = parts
.first()
.unwrap_or(&"Bug report")
.strip_prefix("TITLE: ")
.unwrap_or("Bug report");
let body = parts.get(1).unwrap_or(&"");
assert_eq!(title, "Bug report");
assert_eq!(*body, "");
}
#[test]
fn test_issue_content_parsing_empty() {
let content = "";
let parts: Vec<&str> = content.splitn(2, "\n---\n").collect();
let title = parts
.first()
.unwrap_or(&"Bug report")
.strip_prefix("TITLE: ")
.unwrap_or("Bug report");
let body = parts.get(1).unwrap_or(&"");
assert_eq!(title, "Bug report");
assert_eq!(*body, "");
}
#[test]
fn test_issue_content_parsing_only_separator() {
let content = "\n---\n";
let parts: Vec<&str> = content.splitn(2, "\n---\n").collect();
let title = parts
.first()
.unwrap_or(&"Bug report")
.strip_prefix("TITLE: ")
.unwrap_or("Bug report");
let body = parts.get(1).unwrap_or(&"");
assert_eq!(title, "Bug report");
assert_eq!(*body, "");
}
#[test]
fn test_issue_content_parsing_multiple_separators() {
let content = "TITLE: Title\n---\nFirst section\n---\nSecond section";
let parts: Vec<&str> = content.splitn(2, "\n---\n").collect();
let title = parts
.first()
.unwrap_or(&"Bug report")
.strip_prefix("TITLE: ")
.unwrap_or("Bug report");
let body = parts.get(1).unwrap_or(&"");
assert_eq!(title, "Title");
assert_eq!(*body, "First section\n---\nSecond section");
}
#[test]
fn test_captured_error_creation_for_bug_report() {
let error = CapturedError::new(
"pmat work",
&[
"status".to_string(),
"--format".to_string(),
"json".to_string(),
],
"Failed to connect to database",
);
assert_eq!(error.command, "pmat work");
assert_eq!(error.args.len(), 3);
assert_eq!(error.error_message, "Failed to connect to database");
assert!(error.exit_code.is_none());
}
#[test]
fn test_captured_error_with_exit_code_for_bug_report() {
let error = CapturedError::new("pmat", &["analyze".to_string()], "Analysis failed")
.with_exit_code(42);
assert_eq!(error.exit_code, Some(42));
assert_eq!(error.command, "pmat");
}
#[test]
fn test_captured_error_with_backtrace_for_bug_report() {
let backtrace = " 0: pmat::main\n 1: std::rt::lang_start";
let error =
CapturedError::new("pmat", &["work".to_string()], "Panic").with_backtrace(backtrace);
assert_eq!(error.backtrace, Some(backtrace.to_string()));
}
#[test]
fn test_captured_error_chaining() {
let error = CapturedError::new("pmat", &[], "error")
.with_exit_code(1)
.with_backtrace("trace");
assert_eq!(error.exit_code, Some(1));
assert_eq!(error.backtrace, Some("trace".to_string()));
}
#[test]
fn test_captured_error_metadata() {
let error = CapturedError::new("pmat", &[], "test");
assert!(!error.version.is_empty());
assert!(!error.os.is_empty());
assert!(!error.timestamp.is_empty());
assert!(error.timestamp.contains('T'));
}
#[test]
fn test_captured_error_redact_paths_in_message() {
let home = std::env::var("HOME").unwrap_or_else(|_| "/home/testuser".to_string());
let mut error =
CapturedError::new("pmat", &[], &format!("Error at {}/project/file.rs", home));
error.redact_paths();
assert!(
error.error_message.contains("~"),
"Error message should contain ~ after redaction: {}",
error.error_message
);
assert!(
!error.error_message.contains(&home),
"Error message should not contain home path after redaction"
);
}
#[test]
fn test_captured_error_redact_paths_in_backtrace() {
let home = std::env::var("HOME").unwrap_or_else(|_| "/home/testuser".to_string());
let mut error = CapturedError::new("pmat", &[], "error")
.with_backtrace(&format!(" at {}/project/src/main.rs:42", home));
error.redact_paths();
if let Some(bt) = &error.backtrace {
assert!(bt.contains("~"), "Backtrace should contain ~");
assert!(!bt.contains(&home), "Backtrace should not contain home");
}
}
#[test]
fn test_captured_error_redact_project_path() {
let home = std::env::var("HOME").unwrap_or_else(|_| "/home/testuser".to_string());
let mut error = CapturedError::new("pmat", &[], "error");
error.project_path = Some(format!("{}/project", home));
error.redact_paths();
if let Some(path) = &error.project_path {
assert!(path.contains("~"), "Project path should contain ~");
assert!(
!path.contains(&home),
"Project path should not contain home"
);
}
}
#[test]
fn test_generate_issue_markdown_empty_args() {
let error = CapturedError::new("pmat analyze", &[], "Analysis failed");
let md = generate_issue_markdown(&error, Some("Empty Args Bug"));
assert!(md.contains("TITLE: Empty Args Bug"));
assert!(md.contains("pmat analyze"));
assert!(md.contains("```bash\npmat analyze\n```"));
}
#[test]
fn test_generate_issue_markdown_with_args() {
let error =
CapturedError::new("pmat", &["work".to_string(), "status".to_string()], "Error");
let md = generate_issue_markdown(&error, Some("Test"));
assert!(md.contains("pmat work status"));
}
#[test]
fn test_generate_issue_markdown_all_sections() {
let error = CapturedError::new("pmat", &["test".to_string()], "Test error")
.with_exit_code(1)
.with_backtrace("backtrace content");
let md = generate_issue_markdown(&error, None);
assert!(md.contains("## Summary"));
assert!(md.contains("## Environment"));
assert!(md.contains("## Command Executed"));
assert!(md.contains("## Error Output"));
assert!(md.contains("<details>"));
assert!(md.contains("Backtrace"));
assert!(md.contains("**Exit Code**: 1"));
assert!(md.contains("## Steps to Reproduce"));
assert!(md.contains("## Expected Behavior"));
assert!(md.contains("Generated automatically by `pmat bug-report`"));
}
#[test]
fn test_generate_issue_markdown_default_title_multiword_command() {
let error = CapturedError::new("pmat work status", &[], "Error");
let md = generate_issue_markdown(&error, None);
assert!(md.contains("TITLE: Bug: pmat work fails with error"));
}
#[test]
fn test_generate_issue_markdown_no_optional_fields() {
let mut error = CapturedError::new("pmat", &[], "Error");
error.project_path = None;
let md = generate_issue_markdown(&error, Some("Simple Bug"));
assert!(!md.contains("<details>"));
assert!(!md.contains("Exit Code"));
}
#[test]
fn test_generate_issue_markdown_with_project_path() {
let mut error = CapturedError::new("pmat", &[], "Error");
error.project_path = Some("/path/to/project".to_string());
let md = generate_issue_markdown(&error, Some("Bug"));
assert!(md.contains("**Project Path**: `/path/to/project`"));
}
#[test]
fn test_generate_issue_markdown_special_chars() {
let error = CapturedError::new(
"pmat",
&["--option=value".to_string()],
"Error: `special` <chars> *markdown* _underscore_",
);
let md = generate_issue_markdown(&error, Some("Special Characters"));
assert!(md.contains("`special`"));
assert!(md.contains("<chars>"));
}
}