use std::fs; use serde::Deserialize; #[derive(Debug, Deserialize)] struct BoundaryFamilyEntryDecision { #[serde(rename = "decisionDate")] decision_date: String, scope: String, #[serde(rename = "startingState")] starting_state: StartingState, #[serde(rename = "comparisonMatrix")] comparison_matrix: Vec, #[serde(rename = "selectedCandidate")] selected_candidate: SelectedCandidate, #[serde(rename = "holdReasons")] hold_reasons: Vec, notes: Vec, } #[derive(Debug, Deserialize)] struct StartingState { #[serde(rename = "mainlineClosed")] mainline_closed: Vec, #[serde(rename = "boundaryHeld")] boundary_held: Vec, #[serde(rename = "deferredOutOfScope")] deferred_out_of_scope: Vec, } #[derive(Debug, Deserialize)] struct ComparisonEntry { group: String, #[serde(rename = "representativeScene")] representative_scene: String, #[serde(rename = "entryCondition")] entry_condition: String, #[serde(rename = "smallestNewCapability")] smallest_new_capability: String, #[serde(rename = "entryCost")] entry_cost: String, decision: String, reason: String, } #[derive(Debug, Deserialize)] struct SelectedCandidate { group: String, #[serde(rename = "representativeScene")] representative_scene: String, #[serde(rename = "nextDesign")] next_design: String, #[serde(rename = "nextPlan")] next_plan: String, } #[test] fn boundary_family_entry_roadmap_selects_one_next_candidate() { let decision: BoundaryFamilyEntryDecision = serde_json::from_str( &fs::read_to_string( "tests/fixtures/generated_scene/boundary_family_entry_decision_2026-04-19.json", ) .unwrap(), ) .unwrap(); assert_eq!(decision.decision_date, "2026-04-19"); assert_eq!(decision.scope, "boundary-family-real-sample-entry-roadmap"); assert_eq!( decision.starting_state.mainline_closed, vec!["G1-E", "G2", "G3"] ); assert_eq!( decision.starting_state.boundary_held, vec!["G6", "G7", "G8"] ); assert_eq!( decision.starting_state.deferred_out_of_scope, vec!["G4", "G5"] ); assert_eq!(decision.comparison_matrix.len(), 3); assert_eq!( decision .comparison_matrix .iter() .filter(|entry| entry.decision == "selected") .count(), 1 ); assert!(decision .comparison_matrix .iter() .any(|entry| entry.group == "G7" && entry.decision == "selected" && entry.entry_cost == "medium")); assert!(decision .comparison_matrix .iter() .any(|entry| entry.group == "G6" && entry.decision == "hold" && entry.entry_cost == "high")); assert!(decision .comparison_matrix .iter() .any(|entry| entry.group == "G8" && entry.decision == "hold" && entry.entry_cost == "high")); let selected = &decision.selected_candidate; assert_eq!(selected.group, "G7"); assert_eq!(selected.representative_scene, "计量资产库存统计"); assert!(selected .next_design .ends_with("2026-04-19-g7-real-sample-entry-design.md")); assert!(selected .next_plan .ends_with("2026-04-19-g7-real-sample-entry-plan.md")); assert_eq!(decision.hold_reasons.len(), 2); assert!(!decision.notes.is_empty()); }