use super::*;
#[test]
fn parse_direct_json_happy_path() {
let body = r#"{"verdict":"APPROVE","summary":"Clean change.","findings":[]}"#;
let result = parse_review_response(body);
assert!(
!result.is_fail_safe,
"direct JSON must not trigger fail-safe"
);
assert_eq!(result.verdict, Verdict::Approve);
assert_eq!(result.summary, "Clean change.");
assert!(result.findings.is_empty());
}
#[test]
fn parse_direct_json_request_changes_with_findings() {
let body = serde_json::json!({
"verdict": "REQUEST_CHANGES",
"summary": "SQL injection risk.",
"findings": [
{
"title": "SQL injection",
"body": "Line 42 uses string interpolation in a SQL query.",
"severity": "critical",
"confidence": 0.95,
"file": "src/login.rs",
"line": 42
}
]
})
.to_string();
let result = parse_review_response(&body);
assert!(!result.is_fail_safe, "must not be fail-safe");
assert_eq!(result.verdict, Verdict::RequestChanges);
assert_eq!(result.findings.len(), 1);
assert_eq!(result.findings[0].kind, "SQL injection");
assert_eq!(result.findings[0].file, "src/login.rs");
assert_eq!(result.findings[0].line, Some(42));
}
#[test]
fn parse_direct_json_finding_with_null_line() {
let body = r#"{"verdict":"APPROVE","summary":"ok","findings":[{"title":"t","body":"b","severity":"low","confidence":0.5,"file":"src/a.rs","line":null}]}"#;
let result = parse_review_response(body);
assert!(!result.is_fail_safe);
assert_eq!(result.findings.len(), 1);
assert_eq!(result.findings[0].line, None);
}
const BODY_WITH_JSON_APPROVE: &str = r#"
This PR looks good overall. The authentication logic is straightforward.
```json
{
"verdict": "APPROVE",
"summary": "Clean authentication refactor with no issues.",
"findings": []
}
```
"#;
const BODY_WITH_JSON_REQUEST_CHANGES: &str = r#"
I found a security issue in this PR.
```json
{
"verdict": "REQUEST_CHANGES",
"summary": "SQL injection risk in login handler.",
"findings": [
{
"title": "SQL injection",
"body": "Line 42 uses string interpolation in a SQL query.",
"severity": "critical",
"confidence": 0.95,
"file": "src/login.rs",
"line": 42
}
]
}
```
"#;
const BODY_KEYWORD_ONLY: &str = r#"
After reviewing this PR, I believe the changes look reasonable.
There are some minor style issues but nothing blocking.
The verdict is APPROVE*.
"#;
const BODY_BLOCK_VERDICT: &str = r#"
This PR introduces a critical auth bypass.
BLOCK — this must not merge.
"#;
#[test]
fn parse_json_block_happy_path_approve() {
let result = parse_review_response(BODY_WITH_JSON_APPROVE);
assert!(
!result.is_fail_safe,
"should not be fail-safe: {:?}",
result.fail_safe_reason
);
assert_eq!(result.verdict, Verdict::Approve);
assert_eq!(
result.summary,
"Clean authentication refactor with no issues."
);
assert!(result.findings.is_empty());
}
#[test]
fn parse_json_block_happy_path_request_changes() {
let result = parse_review_response(BODY_WITH_JSON_REQUEST_CHANGES);
assert!(!result.is_fail_safe);
assert_eq!(result.verdict, Verdict::RequestChanges);
assert_eq!(result.findings.len(), 1);
let f = &result.findings[0];
assert_eq!(f.kind, "SQL injection");
assert_eq!(f.file, "src/login.rs");
assert_eq!(f.line, Some(42));
assert!((f.confidence - 0.95_f32).abs() < 1e-5);
}
#[test]
fn parse_verdict_keyword_fallback_approve_star() {
let result = parse_review_response(BODY_KEYWORD_ONLY);
assert!(!result.is_fail_safe);
assert_eq!(result.verdict, Verdict::ApproveWithReservations);
assert!(result.findings.is_empty());
}
#[test]
fn parse_verdict_keyword_fallback_block() {
let result = parse_review_response(BODY_BLOCK_VERDICT);
assert!(!result.is_fail_safe);
assert_eq!(result.verdict, Verdict::Block);
}
#[test]
fn parse_fail_safe_approve_on_empty_response() {
let result = parse_review_response("");
assert!(result.is_fail_safe, "empty response must trigger fail-safe");
assert_eq!(
result.verdict,
Verdict::Approve,
"fail-safe must default to APPROVE"
);
assert!(result.fail_safe_reason.is_some());
}
#[test]
fn parse_fail_safe_approve_on_malformed_json() {
let body = r#"This is a review response with no verdict.
```json
{ "verdict": "definitely yes", "this_is": broken json
"#;
let result = parse_review_response(body);
assert_eq!(result.verdict, Verdict::Approve);
assert!(
result.is_fail_safe,
"malformed JSON with no keyword must be fail-safe"
);
}
#[test]
fn parse_fail_safe_approve_on_unparseable_verdict() {
let body = r#"```json
{"verdict": "LOOKS_OK", "summary": "fine", "findings": []}
```"#;
let result = parse_review_response(body);
assert_eq!(result.verdict, Verdict::Approve);
}
#[test]
fn parse_verdict_string_normalization() {
assert_eq!(parse_verdict_string("approve"), Some(Verdict::Approve));
assert_eq!(parse_verdict_string("APPROVE"), Some(Verdict::Approve));
assert_eq!(
parse_verdict_string(" REQUEST_CHANGES "),
Some(Verdict::RequestChanges)
);
assert_eq!(parse_verdict_string("block"), Some(Verdict::Block));
assert_eq!(parse_verdict_string("UNKNOWN"), Some(Verdict::Unknown));
assert_eq!(parse_verdict_string("unknown"), Some(Verdict::Unknown));
assert_eq!(parse_verdict_string("N/A"), None);
}
#[test]
fn parse_json_block_handles_fence_variants() {
let body = r#"
First example:
```json
{"verdict": "BLOCK", "summary": "not the last one", "findings": []}
```
Second example:
```json
{"verdict": "APPROVE", "summary": "this is the last one", "findings": []}
```
"#;
let result = parse_review_response(body);
assert_eq!(result.verdict, Verdict::Approve);
assert_eq!(result.summary, "this is the last one");
}
#[test]
fn parse_findings_confidence_clamped() {
let body = r#"```json
{
"verdict": "REQUEST_CHANGES",
"summary": "test",
"findings": [
{"title": "t", "body": "b", "severity": "low", "confidence": 2.5, "file": "a.rs"}
]
}
```"#;
let result = parse_review_response(body);
assert_eq!(result.findings.len(), 1);
assert!(
result.findings[0].confidence <= 1.0,
"confidence must be clamped: {}",
result.findings[0].confidence
);
}
#[test]
fn parse_finding_missing_file_defaults_to_unknown() {
let body = r#"```json
{
"verdict": "APPROVE",
"summary": "ok",
"findings": [{"title": "t", "body": "b"}]
}
```"#;
let result = parse_review_response(body);
assert_eq!(result.findings[0].file, "unknown");
}
#[test]
fn scan_verdict_keyword_priority_block_beats_approve() {
let body = "This APPROVE-worthy PR unfortunately has a BLOCK issue.";
let verdict = scan_verdict_keyword(body);
assert_eq!(verdict, Some(Verdict::Block));
}
#[test]
fn parse_direct_json_unknown_verdict() {
let body = r#"{"verdict":"UNKNOWN","summary":"Diff too truncated to assess.","findings":[]}"#;
let result = parse_review_response(body);
assert!(
!result.is_fail_safe,
"UNKNOWN from model must not trigger fail-safe"
);
assert_eq!(
result.verdict,
Verdict::Unknown,
"parser must preserve UNKNOWN from model output"
);
}
#[test]
fn scan_verdict_keyword_detects_unknown() {
let body = "The diff is too short to assess. UNKNOWN";
let verdict = scan_verdict_keyword(body);
assert_eq!(verdict, Some(Verdict::Unknown));
}
#[test]
fn parse_direct_json_approve_star() {
let body = r#"{"verdict":"APPROVE*","summary":"Minor concern noted.","findings":[]}"#;
let result = parse_review_response(body);
assert!(!result.is_fail_safe);
assert_eq!(result.verdict, Verdict::ApproveWithReservations);
}