//! `diagnose::session` 的单测(拆文件守 400 行上限)。 use super::*; use crate::launch::redact::Redactor; /// `fabric.loader_0_12`(critical / priority 80)在 `rules.d/fabric.yaml` 里的唯一证据行。 const EVIDENCE: &str = "java.lang.NoClassDefFoundError: \ org/spongepowered/asm/mixin/transformer/FabricMixinTransformerProxy"; fn facts<'a>(stdout: &'a [String], stderr: &'a [String]) -> RawFacts<'a> { RawFacts { exit_code: Some(1), duration_ms: 4_200, error_lines: 2, stdout, stderr, } } fn record_id(record: &Value) -> Option<&str> { record .get("candidates") .and_then(Value::as_array) .and_then(|items| items.first()) .and_then(|item| item.get("id")) .and_then(Value::as_str) } /// **判据 ①**:原始证据行能命中 `fabric.loader_0_12`;同一行被脱敏后命不中。 /// /// 后半句不是「补充说明」,它是这个模块存在的理由——会话 JSONL 里只有脱敏文本。 #[test] fn raw_evidence_hits_the_fabric_rule_while_scrubbed_evidence_misses_it() { let raw = [EVIDENCE.to_string()]; let empty: [String; 0] = []; let record = conclude(&facts(&empty, &raw)).expect("原始证据应当有结论"); assert_eq!(record_id(&record), Some("fabric.loader_0_12"), "{record}"); assert_eq!(record["source"], serde_json::json!("rules")); assert_eq!(record["needs_more_info"], serde_json::json!(false)); let scrubbed = [Redactor::new().scrub(EVIDENCE).into_owned()]; assert!( scrubbed[0].contains(""), "夹具前提:这条证据行确实会被抹:{}", scrubbed[0] ); let after = conclude(&facts(&empty, &scrubbed)); assert_ne!( after.as_ref().and_then(record_id), Some("fabric.loader_0_12"), "脱敏后的文本不该再命中——这就是 M239 报的那个 bug" ); } /// 没有文本就没有结论(不写空事件)。 #[test] fn no_text_means_no_conclusion() { let empty: [String; 0] = []; assert!(conclude(&facts(&empty, &empty)).is_none()); } /// 认不出来的日志也不写事件(让读回 JSONL 的兜底路径去跑,避免两条路给出不同结论)。 #[test] fn unrecognized_output_means_no_conclusion() { let stdout = ["[12:00:00] [main/INFO]: Setting user".to_string()]; let empty: [String; 0] = []; assert!(conclude(&facts(&stdout, &empty)).is_none()); } /// 结论里带的是**已渲染**的文案与物化动作,展示侧不必再自己拼。 #[test] fn the_record_carries_rendered_text_and_actions() { let raw = ["Fabric loader failed".to_string(), EVIDENCE.to_string()]; let empty: [String; 0] = []; let record = conclude(&facts(&empty, &raw)).expect("应有结论"); let candidate = &record["candidates"][0]; assert!(!candidate["message"].as_str().unwrap_or_default().is_empty()); assert_eq!( candidate["fix_actions"][0]["kind"], serde_json::json!("reinstall") ); assert!( candidate["evidence"][0] .as_str() .unwrap_or_default() .contains("NoClassDefFoundError"), "{candidate}" ); }