use super::*;
use allow_core::{
AllowEntry, Finding, FindingKind, LastSeen, Lifecycle, Selector, Span, StructuralIdentity,
};
use std::path::PathBuf;
#[test]
fn add_json_renderer_records_entry_and_selected_finding() {
let entry = AllowEntry {
id: "allow-add-json".to_string(),
kind: FindingKind::Panic,
family: Some("unwrap".to_string()),
path: Some(PathBuf::from("src\\lib.rs")),
glob: None,
owner: "parser".to_string(),
classification: "reviewed_exception".to_string(),
reason: "Parser validates input before unwrapping.".to_string(),
evidence: vec!["test:parser_validates_input".to_string()],
links: Vec::new(),
occurrence_limit: None,
lifecycle: Lifecycle {
created: Some("2026-05-27".to_string()),
review_after: Some("2026-11-01".to_string()),
expires: Some("2027-01-01".to_string()),
},
selector: Selector {
ast_kind: Some("method_call".to_string()),
container: Some("parse_span".to_string()),
callee: Some("unwrap".to_string()),
macro_name: None,
lint: None,
symbol: Some("value.unwrap()".to_string()),
receiver_fingerprint: None,
target_fingerprint: None,
normalized_snippet_hash: Some("fnv1a64:add".to_string()),
line_hint: Some(42),
glob: None,
},
last_seen: Some(LastSeen {
line: 42,
column: 13,
}),
};
let mut identity = StructuralIdentity::new("rust", "method_call");
identity.crate_name = Some("parser".to_string());
identity.container = Some("parse_span".to_string());
identity.callee = Some("unwrap".to_string());
let finding = Finding {
kind: FindingKind::Panic,
family: Some("unwrap".to_string()),
path: PathBuf::from("src\\lib.rs"),
span: Some(Span {
line: 42,
column: 13,
}),
identity,
message: "unwrap call".to_string(),
};
let json = render_add_json(AddReport::new(
InventoryContext::source_syntax("git_tracked", Some("H:/Code/Rust/cargo-allow"), Some(52)),
&entry,
&finding,
Some("policy/allow.proposed.toml"),
true,
));
assert!(json.contains("\"schema_id\": \"cargo-allow.add.v1\""));
assert!(json.contains("\"command\": \"add\""));
assert!(json.contains("\"source\": \"git_tracked\""));
assert!(json.contains("\"root\": \"H:/Code/Rust/cargo-allow\""));
assert!(json.contains("\"files_scanned\": 52"));
assert!(json.contains("\"policy_output\": \"policy/allow.proposed.toml\""));
assert!(json.contains("\"force\": true"));
assert!(json.contains("\"entry_id\": \"allow-add-json\""));
assert!(json.contains("\"selected_finding\": \"src/lib.rs:42:13\""));
assert!(json.contains("\"human_review_required\": true"));
assert!(json.contains("\"id\": \"allow-add-json\""));
assert!(json.contains("\"path\": \"src/lib.rs\""));
assert!(json.contains("\"review_after\": \"2026-11-01\""));
assert!(json.contains("\"expires\": \"2027-01-01\""));
assert!(json.contains("\"evidence_count\": 1"));
assert!(json.contains("\"source_package\": \"parser\""));
assert!(json.contains("\"normalized_snippet_hash\": \"fnv1a64:add\""));
let expected = format!(
r#"{{
"schema_version": 1,
"schema_id": "cargo-allow.add.v1",
"tool": "cargo-allow",
"command": "add",
"claim_boundary": {},
"scanner_limitations": {},
"inventory": {{
"scope": "source_tree",
"scanner": "source_syntax",
"source": "git_tracked",
"root": "H:/Code/Rust/cargo-allow",
"files_scanned": 52
}},
"options": {{
"policy_output": "policy/allow.proposed.toml",
"force": true
}},
"summary": {{
"entry_id": "allow-add-json",
"selected_finding": "src/lib.rs:42:13",
"human_review_required": true
}},
"allow_entry": {{
"id": "allow-add-json",
"kind": "panic",
"family": "unwrap",
"path": "src/lib.rs",
"glob": null,
"owner": "parser",
"classification": "reviewed_exception",
"reason": "Parser validates input before unwrapping.",
"review_after": "2026-11-01",
"expires": "2027-01-01",
"evidence_count": 1,
"selector": {{
"ast_kind": "method_call",
"container": "parse_span",
"callee": "unwrap",
"macro_name": null,
"lint": null,
"symbol": "value.unwrap()",
"receiver_fingerprint": null,
"target_fingerprint": null,
"normalized_snippet_hash": "fnv1a64:add",
"line_hint": 42,
"glob": null
}},
"last_seen": {{
"line": 42,
"column": 13
}}
}},
"selected_finding": {{
"status": "selected",
"kind": "panic",
"family": "unwrap",
"path": "src/lib.rs",
"line": 42,
"column": 13,
"source_package": "parser",
"identity": {{
"language": "rust",
"crate_name": "parser",
"module": null,
"container": "parse_span",
"ast_kind": "method_call",
"symbol": null,
"callee": "unwrap",
"macro_name": null,
"lint": null,
"receiver_fingerprint": null,
"target_fingerprint": null,
"normalized_snippet_hash": null,
"line_hint": null,
"column_hint": null
}},
"message": "unwrap call"
}}
}}
"#,
render_claim_boundary_json(),
render_scanner_limitations_json()
);
assert_eq!(json, expected);
let text = render_add_human(AddReport::new(
InventoryContext::source_syntax("git_tracked", Some("H:/Code/Rust/cargo-allow"), Some(52)),
&entry,
&finding,
Some("policy/allow.proposed.toml"),
false,
));
assert!(text.contains("cargo-allow add summary"));
assert!(
text.contains("inventory: source_tree/source_syntax via git_tracked; files scanned: 52")
);
assert!(text.contains("source_tree_root: H:/Code/Rust/cargo-allow"));
assert!(text.contains("id: allow-add-json"));
assert!(text.contains("kind: panic"));
assert!(text.contains("family: unwrap"));
assert!(text.contains("matched finding: src/lib.rs:42:13"));
assert!(text.contains("output: policy/allow.proposed.toml"));
assert!(text.contains("requires human review"));
assert!(text.contains("Claim boundary: scanned source-tree/source syntax only"));
}