//! Round-trip stability for the `state.ncl` renderer (O1.5 / D9). //! //! Strategy: load the real `.ontology/state.ncl`, render it, re-parse the //! rendered bytes, render again. The second render MUST produce the same //! bytes as the first — confirming `render(parse(render(x)))` is a fixed //! point. This is strictly stronger than a structural-equivalence check //! because it inspects every byte of the canonical form. use std::path::{Path, PathBuf}; use std::process::Command; use ontoref_ontology::render::render_state_ncl; use ontoref_ontology::types::StateConfig; /// Project root that holds the `.ontoref/` spine. /// /// Walks up from this crate until it finds the directory containing `.ontoref/`. /// Post-ADR-048 (constellation) the spine sits at the constellation parent, /// outside `code/`; pre-ADR-048 it was inside the workspace. Searching ancestors /// resolves it under either layout instead of hard-coding a hop count. fn workspace_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .ancestors() .find(|dir| dir.join(".ontoref").is_dir()) .map(Path::to_path_buf) .expect("a `.ontoref/` spine must exist in an ancestor of CARGO_MANIFEST_DIR") } fn nickel_export_state(path: &Path) -> StateConfig { let output = Command::new("nickel") .arg("export") .arg("--format") .arg("json") .arg(path) .output() .expect("nickel binary must be on PATH for this test"); assert!( output.status.success(), "nickel export failed for {}: {}", path.display(), String::from_utf8_lossy(&output.stderr) ); serde_json::from_slice(&output.stdout).expect("state.ncl JSON deserialises to StateConfig") } #[test] fn render_then_reparse_then_render_is_idempotent() { let root = workspace_root(); let source = ontoref_ontology::layout::resolve_section(&root, "ontology/state.ncl"); let initial = nickel_export_state(&source); let bytes_a = render_state_ncl(&initial); let tmp = tempfile::tempdir().expect("tempdir"); let staged = tmp.path().join("state.ncl"); std::fs::write(&staged, &bytes_a).expect("write staged"); let reparsed = nickel_export_state(&staged); let bytes_b = render_state_ncl(&reparsed); assert_eq!( bytes_a, bytes_b, "render(parse(render(state))) must produce identical bytes" ); }