use git_iris::agents::TaskContext;
use git_iris::{
Category, EvidenceRef, Finding, FindingId, Review, ReviewMetadata, ReviewStats, RiskLevel,
Severity,
};
use std::path::PathBuf;
fn sample_finding() -> Finding {
Finding {
id: FindingId("finding-1".to_string()),
severity: Severity::High,
confidence: 86,
file: PathBuf::from("src/auth.rs"),
start_line: 42,
end_line: 44,
category: Category::Security,
title: "Missing authorization check".to_string(),
body: "The changed handler accepts user input before checking access.".to_string(),
suggested_fix: Some("Check authorization before processing the request.".to_string()),
evidence: vec![EvidenceRef {
file: PathBuf::from("src/auth.rs"),
line: 42,
end_line: Some(44),
note: Some("changed handler".to_string()),
}],
}
}
fn low_confidence_finding() -> Finding {
Finding {
confidence: 42,
severity: Severity::Medium,
id: FindingId("finding-2".to_string()),
file: PathBuf::from("src/auth.rs"),
start_line: 50,
end_line: 50,
category: Category::Testing,
title: "Possible missing test".to_string(),
body: "This is too speculative to publish.".to_string(),
suggested_fix: None,
evidence: Vec::new(),
}
}
fn overconfident_finding() -> Finding {
Finding {
confidence: 200,
..sample_finding()
}
}
fn finding_from_confidence(confidence: impl Into<serde_json::Value>) -> Finding {
let confidence = confidence.into();
serde_json::from_value(serde_json::json!({
"id": "finding-1",
"severity": "high",
"confidence": confidence,
"file": "src/auth.rs",
"start_line": 42,
"end_line": 42,
"category": "security",
"title": "Missing authorization check",
"body": "The changed handler accepts user input before checking access."
}))
.expect("finding should deserialize")
}
#[test]
fn structured_review_renders_markdown_from_findings() {
let finding = sample_finding();
let review = Review {
summary: "Adds an auth handler.".to_string(),
metadata: ReviewMetadata::default(),
findings: vec![finding],
stats: ReviewStats::default(),
parse_failed: false,
};
let markdown = review.raw_content();
assert!(markdown.contains("# Code Review"));
assert!(markdown.contains("Adds an auth handler."));
assert!(markdown.contains("[HIGH] **Missing authorization check in `src/auth.rs:42-44`**"));
assert!(markdown.contains("Category: security. Confidence: 86%."));
assert!(markdown.contains("Evidence: src/auth.rs:42-44 (changed handler)"));
}
#[test]
fn structured_review_renders_agentic_metadata() {
let review = Review {
summary: "Review summary".to_string(),
metadata: ReviewMetadata {
risk_level: Some(RiskLevel::High),
strategy: "Split auth and storage into separate specialist passes.".to_string(),
specialist_passes: vec![
"Security/auth validation".to_string(),
"Storage API compatibility".to_string(),
],
coverage_notes: vec!["Verified changed tests and public call sites.".to_string()],
},
findings: Vec::new(),
stats: ReviewStats::default(),
parse_failed: false,
};
let markdown = review.raw_content();
assert!(markdown.contains("## Review Coverage"));
assert!(markdown.contains("Risk: high"));
assert!(markdown.contains("Strategy: Split auth and storage"));
assert!(markdown.contains("- Security/auth validation"));
assert!(markdown.contains("- Verified changed tests and public call sites."));
}
#[test]
fn review_metadata_risk_level_accepts_normalized_values() {
let metadata: ReviewMetadata = serde_json::from_value(serde_json::json!({
"risk_level": "HIGH ",
"strategy": "Checked changed APIs."
}))
.expect("metadata should deserialize");
assert_eq!(metadata.risk_level, Some(RiskLevel::High));
}
#[test]
fn review_metadata_skips_blank_values() {
let metadata = ReviewMetadata {
risk_level: None,
strategy: " ".to_string(),
specialist_passes: vec![" ".to_string()],
coverage_notes: vec![String::new()],
};
let encoded = serde_json::to_value(&metadata).expect("metadata should serialize");
assert_eq!(encoded, serde_json::json!({}));
assert!(metadata.is_empty());
}
#[test]
fn parse_failed_reviews_do_not_render_metadata() {
let review = Review {
metadata: ReviewMetadata {
risk_level: Some(RiskLevel::Critical),
strategy: "Should not appear in failure output.".to_string(),
specialist_passes: Vec::new(),
coverage_notes: Vec::new(),
},
..Review::from_unstructured("{bad json}")
};
let markdown = review.raw_content();
assert!(!markdown.contains("## Review Coverage"));
assert!(!markdown.contains("Should not appear"));
}
#[test]
fn review_stats_are_derived_when_model_counts_are_missing() {
let review = Review {
summary: String::new(),
metadata: ReviewMetadata::default(),
findings: vec![sample_finding()],
stats: ReviewStats::default(),
parse_failed: false,
};
let stats = review.effective_stats();
assert_eq!(stats.findings_count, 1);
assert_eq!(stats.high_count, 1);
}
#[test]
fn low_confidence_findings_do_not_render() {
let review = Review {
summary: String::new(),
metadata: ReviewMetadata::default(),
findings: vec![low_confidence_finding()],
stats: ReviewStats::default(),
parse_failed: false,
};
let markdown = review.raw_content();
assert!(markdown.contains("Found 0 issue(s)"));
assert!(markdown.contains("No blocking issues found."));
assert!(!markdown.contains("Possible missing test"));
}
#[test]
fn confidence_scores_are_clamped_for_rendering_and_gates() {
let review = Review {
summary: String::new(),
metadata: ReviewMetadata::default(),
findings: vec![overconfident_finding()],
stats: ReviewStats::default(),
parse_failed: false,
};
let markdown = review.raw_content();
assert!(markdown.contains("Confidence: 100%."));
assert_eq!(review.visible_findings_at(101).len(), 0);
}
#[test]
fn category_accepts_common_aliases() {
for (raw, expected) in [
("\"error-handling\"", Category::ErrorHandling),
("\"errorHandling\"", Category::ErrorHandling),
("\"ERROR HANDLING\"", Category::ErrorHandling),
("\"Security\"", Category::Security),
("\"performance \"", Category::Performance),
("\"api contract\"", Category::ApiContract),
("\"surprise\"", Category::Other),
] {
let category: Category = serde_json::from_str(raw).expect("category should deserialize");
assert_eq!(category, expected);
}
}
#[test]
fn unstructured_review_fallback_does_not_claim_success() {
let review = Review::from_unstructured("{bad json}\n```nested```");
let markdown = review.raw_content();
assert!(markdown.contains("Review parsing failed"));
assert!(markdown.contains("{bad json}"));
assert!(markdown.contains("```text"));
assert!(markdown.contains("`\\`\\`nested`\\`\\`"));
assert!(!markdown.contains("No blocking issues found."));
assert!(!markdown.contains("Found 0 issue(s)"));
}
#[test]
fn parse_failed_reviews_round_trip() {
let review = Review::from_unstructured("{bad json}");
let encoded = serde_json::to_string(&review).expect("review should serialize");
let decoded: Review = serde_json::from_str(&encoded).expect("review should deserialize");
assert!(decoded.parse_failed);
assert!(!decoded.raw_content().contains("No blocking issues found."));
}
#[test]
fn confidence_accepts_common_model_shapes() {
assert_eq!(
finding_from_confidence(serde_json::json!(0.85)).confidence,
85
);
assert_eq!(
finding_from_confidence(serde_json::json!("95.0")).confidence,
95
);
assert_eq!(
finding_from_confidence(serde_json::json!("72%")).confidence,
72
);
assert_eq!(finding_from_confidence(serde_json::json!(1)).confidence, 1);
assert_eq!(
finding_from_confidence(serde_json::json!(250)).confidence,
100
);
assert_eq!(finding_from_confidence(serde_json::json!(-5)).confidence, 0);
}
#[test]
fn test_branch_parameter_validation() {
let staged = TaskContext::for_review(None, None, None, false).expect("should succeed");
assert!(matches!(
staged,
TaskContext::Staged {
include_unstaged: false
}
));
let staged_with_unstaged =
TaskContext::for_review(None, None, None, true).expect("should succeed");
assert!(matches!(
staged_with_unstaged,
TaskContext::Staged {
include_unstaged: true
}
));
let commit = TaskContext::for_review(Some("abc123".to_string()), None, None, false)
.expect("should succeed");
assert!(matches!(commit, TaskContext::Commit { commit_id } if commit_id == "abc123"));
let explicit_range = TaskContext::for_review(
None,
Some("main".to_string()),
Some("feature".to_string()),
false,
)
.expect("should succeed");
assert!(
matches!(explicit_range, TaskContext::Range { from, to } if from == "main" && to == "feature")
);
let to_only =
TaskContext::for_review_with_base(None, None, Some("feature".to_string()), false, "trunk")
.expect("should succeed");
assert!(
matches!(to_only, TaskContext::Range { from, to } if from == "trunk" && to == "feature")
);
let from_only = TaskContext::for_review(None, Some("main".to_string()), None, false);
assert!(from_only.is_err());
assert!(
from_only
.expect_err("should fail")
.to_string()
.contains("When using --from, you must also specify --to")
);
let commit_with_range = TaskContext::for_review(
Some("abc123".to_string()),
Some("main".to_string()),
Some("feature".to_string()),
false,
);
assert!(commit_with_range.is_err());
assert!(
commit_with_range
.expect_err("should fail")
.to_string()
.contains("mutually exclusive")
);
let to_with_commit = TaskContext::for_review(
Some("abc123".to_string()),
None,
Some("feature".to_string()),
false,
);
assert!(to_with_commit.is_err());
let unstaged_with_range = TaskContext::for_review(
None,
Some("main".to_string()),
Some("feature".to_string()),
true,
);
assert!(unstaged_with_range.is_err());
assert!(
unstaged_with_range
.expect_err("should fail")
.to_string()
.contains("include-unstaged")
);
let unstaged_with_to_only =
TaskContext::for_review(None, None, Some("feature".to_string()), true);
assert!(unstaged_with_to_only.is_err());
}