//! Layer 1: IR serialization snapshots (Sprint 20). //! //! Uses `insta` to capture stable JSON representations of IR structs. //! On first run these create `.snap.new` files — run `cargo insta review` to approve. //! Snapshots live in `tests/snapshots/` and are committed to version control. //! //! Purpose: catch accidental schema changes that break saved project files. use cimery_core::{ AbutmentType, BearingType, ColumnShape, CrossBeamSection, ExpansionJointType, MaterialGrade, PierType, SectionType, }; use cimery_ir::{ AbutmentIR, BearingIR, CapBeamIR, CrossBeamIR, DeckSlabIR, ExpansionJointIR, FeatureId, GirderIR, PierIR, PscISectionParams, SectionParams, WingWallIR, }; // ─── Helpers ────────────────────────────────────────────────────────────────── /// GirderIR with a deterministic fixed ID for stable snapshots. fn snapshot_girder() -> GirderIR { GirderIR { id: fixed_id("girder-1"), station_start: 100.0, station_end: 140.0, offset_from_alignment: 0.0, section_type: SectionType::PscI, section: SectionParams::PscI(PscISectionParams::kds_standard()), count: 5, spacing: 2500.0, material: MaterialGrade::C50, } } fn snapshot_deck_slab() -> DeckSlabIR { DeckSlabIR { id: fixed_id("deck-1"), station_start: 100.0, station_end: 140.0, width_left: 5_500.0, width_right: 5_500.0, thickness: 220.0, haunch_depth: 100.0, cross_slope: 2.0, material: MaterialGrade::C40, } } fn snapshot_bearing() -> BearingIR { BearingIR { id: fixed_id("bearing-1"), station: 100.0, bearing_type: BearingType::Elastomeric, plan_length: 350.0, plan_width: 350.0, total_height: 90.0, capacity_vertical:2_500.0, } } fn snapshot_pier() -> PierIR { PierIR { id: fixed_id("pier-1"), station: 120.0, skew_angle: 0.0, pier_type: PierType::SingleColumn, column_shape: ColumnShape::Circular, column_count: 1, column_spacing: 0.0, column_diameter: 1_500.0, column_depth: 0.0, column_height: 8_000.0, cap_beam: CapBeamIR { length: 13_000.0, width: 1_200.0, depth: 1_400.0, cantilever_left: 1_000.0, cantilever_right:1_000.0, }, material: MaterialGrade::C40, } } fn snapshot_abutment() -> AbutmentIR { AbutmentIR { id: fixed_id("abutment-1"), station: 100.0, skew_angle: 0.0, abutment_type: AbutmentType::ReverseT, breast_wall_height: 5_000.0, breast_wall_thickness: 800.0, breast_wall_width: 12_000.0, footing_length: 4_000.0, footing_width: 13_000.0, footing_thickness: 1_000.0, wing_wall_left: WingWallIR { length: 4_000.0, height: 4_000.0, thickness: 500.0 }, wing_wall_right: WingWallIR { length: 4_000.0, height: 4_000.0, thickness: 500.0 }, material: MaterialGrade::C40, } } fn snapshot_cross_beam() -> CrossBeamIR { CrossBeamIR { id: fixed_id("cb-1"), station: 110.0, section: CrossBeamSection::HSection, web_height: 1_260.0, web_thickness: 12.0, flange_width: 300.0, flange_thickness: 16.0, bay_count: 4, girder_spacing: 2_500.0, material: MaterialGrade::Ss400, } } fn snapshot_expansion_joint() -> ExpansionJointIR { ExpansionJointIR { id: fixed_id("ej-1"), station: 100.0, joint_type: ExpansionJointType::RubberType, gap_width: 50.0, total_width: 11_000.0, depth: 100.0, movement_range: 30.0, } } /// Build a FeatureId from a deterministic UUID-v5-like string (not actually v5, /// just a fixed nil-offset trick for snapshot stability). fn fixed_id(tag: &str) -> FeatureId { use std::str::FromStr; // Seed with a stable tag-based UUID string (each test feature has its own). let s = match tag { "girder-1" => "00000000-0000-0000-0000-000000000001", "deck-1" => "00000000-0000-0000-0000-000000000002", "bearing-1" => "00000000-0000-0000-0000-000000000003", "pier-1" => "00000000-0000-0000-0000-000000000004", "abutment-1" => "00000000-0000-0000-0000-000000000005", "cb-1" => "00000000-0000-0000-0000-000000000006", "ej-1" => "00000000-0000-0000-0000-000000000007", _ => "00000000-0000-0000-0000-000000000000", }; FeatureId(uuid::Uuid::from_str(s).unwrap()) } // ─── Snapshot tests ─────────────────────────────────────────────────────────── #[test] fn snapshot_girder_ir() { let json = serde_json::to_string_pretty(&snapshot_girder()).unwrap(); insta::assert_snapshot!("girder_ir_json", json); } #[test] fn snapshot_deck_slab_ir() { let json = serde_json::to_string_pretty(&snapshot_deck_slab()).unwrap(); insta::assert_snapshot!("deck_slab_ir_json", json); } #[test] fn snapshot_bearing_ir() { let json = serde_json::to_string_pretty(&snapshot_bearing()).unwrap(); insta::assert_snapshot!("bearing_ir_json", json); } #[test] fn snapshot_pier_ir() { let json = serde_json::to_string_pretty(&snapshot_pier()).unwrap(); insta::assert_snapshot!("pier_ir_json", json); } #[test] fn snapshot_abutment_ir() { let json = serde_json::to_string_pretty(&snapshot_abutment()).unwrap(); insta::assert_snapshot!("abutment_ir_json", json); } #[test] fn snapshot_cross_beam_ir() { let json = serde_json::to_string_pretty(&snapshot_cross_beam()).unwrap(); insta::assert_snapshot!("cross_beam_ir_json", json); } #[test] fn snapshot_expansion_joint_ir() { let json = serde_json::to_string_pretty(&snapshot_expansion_joint()).unwrap(); insta::assert_snapshot!("expansion_joint_ir_json", json); } // ─── Round-trip sanity ──────────────────────────────────────────────────────── #[test] fn girder_json_roundtrip() { let ir = snapshot_girder(); let json = serde_json::to_string(&ir).unwrap(); let ir2: GirderIR = serde_json::from_str(&json).unwrap(); assert!((ir.span_m() - ir2.span_m()).abs() < f64::EPSILON); assert_eq!(ir.count, ir2.count); } #[test] fn cross_beam_json_roundtrip() { let ir = snapshot_cross_beam(); let json = serde_json::to_string(&ir).unwrap(); let ir2: CrossBeamIR = serde_json::from_str(&json).unwrap(); assert_eq!(ir.bay_count, ir2.bay_count); assert!((ir.girder_spacing - ir2.girder_spacing).abs() < f64::EPSILON); assert!((ir.total_length_mm() - ir2.total_length_mm()).abs() < f64::EPSILON); } #[test] fn expansion_joint_json_roundtrip() { let ir = snapshot_expansion_joint(); let json = serde_json::to_string(&ir).unwrap(); let ir2: ExpansionJointIR = serde_json::from_str(&json).unwrap(); assert!((ir.gap_width - ir2.gap_width).abs() < f64::EPSILON); assert!((ir.total_width - ir2.total_width).abs() < f64::EPSILON); }