use std::fs;
use std::path::Path;
use super::hydrate::{hydrate_graph_geometry_from_mmds, hydrate_routed_geometry_from_mmds};
use super::{HydrationError, evaluate_profiles, from_str};
use crate::graph::{Direction, Shape};
#[test]
fn hydration_applies_defaults_to_omitted_node_and_edge_fields() {
let payload = mmds_fixture("defaults-minimal.json");
let diagram = from_str(&payload).expect("valid hydration");
assert_eq!(diagram.nodes["A"].shape, Shape::Round);
assert_eq!(diagram.edges[0].minlen, 2);
}
#[test]
fn hydration_maps_direction_subgraphs_and_minlen() {
let payload = mmds_fixture("layout-with-subgraphs.json");
let diagram = from_str(&payload).expect("valid hydration");
assert_eq!(diagram.direction, Direction::LeftRight);
assert_eq!(diagram.edges[0].minlen, 2);
assert_eq!(diagram.edges[0].label.as_deref(), Some("go"));
assert!(diagram.subgraphs.contains_key("sg1"));
assert!(diagram.subgraphs.contains_key("sg2"));
assert_eq!(diagram.subgraphs["sg1"].dir, Some(Direction::BottomTop));
assert_eq!(diagram.subgraphs["sg2"].parent.as_deref(), Some("sg1"));
}
#[test]
fn hydration_reconstructs_compound_membership_for_nested_subgraphs() {
let payload = mmds_fixture("layout-with-subgraphs.json");
let diagram = from_str(&payload).expect("valid hydration");
assert_eq!(
diagram.subgraphs["sg1"].nodes,
vec!["B".to_string(), "C".to_string()]
);
assert_eq!(diagram.subgraphs["sg2"].nodes, vec!["C".to_string()]);
}
#[test]
fn hydration_compound_membership_order_is_deterministic() {
let payload = mmds_fixture("layout-with-subgraphs.json");
let first = from_str(&payload).expect("valid hydration");
let second = from_str(&payload).expect("valid hydration");
assert_eq!(first.subgraphs["sg1"].nodes, second.subgraphs["sg1"].nodes);
assert_eq!(first.subgraphs["sg2"].nodes, second.subgraphs["sg2"].nodes);
}
#[test]
fn hydration_rejects_dangling_edge_reference() {
let payload = invalid_fixture("dangling-edge-target.json");
let err = from_str(&payload).unwrap_err();
assert!(matches!(err, HydrationError::DanglingEdgeTarget { .. }));
}
#[test]
fn hydration_rejects_dangling_subgraph_endpoint_intent_reference() {
let payload = invalid_fixture("dangling-endpoint-intent-subgraph.json");
let err = from_str(&payload).unwrap_err();
assert!(matches!(
err,
HydrationError::DanglingEdgeToSubgraphIntent { .. }
));
}
#[test]
fn hydration_rejects_missing_required_id() {
let payload = invalid_fixture("missing-node-id.json");
let err = from_str(&payload).unwrap_err();
assert!(matches!(err, HydrationError::MissingNodeId { .. }));
}
#[test]
fn hydration_rejects_invalid_enum_value() {
let payload = invalid_fixture("invalid-shape.json");
let err = from_str(&payload).unwrap_err();
assert!(matches!(err, HydrationError::InvalidShape { .. }));
}
#[test]
fn hydration_rejects_cyclic_subgraph_parent_chain() {
let payload = invalid_fixture("cyclic-subgraph-parent.json");
let err = from_str(&payload).unwrap_err();
assert!(matches!(
err,
HydrationError::CyclicSubgraphParentChain { .. }
));
}
#[test]
fn hydration_rejects_unsupported_mmds_core_version() {
let payload = invalid_fixture("unsupported-version.json");
let err = from_str(&payload).unwrap_err();
assert!(matches!(err, HydrationError::UnsupportedVersion { .. }));
}
#[test]
fn hydration_preserves_deterministic_edge_order_by_edge_id() {
let payload = mmds_fixture("layout-unsorted-edges.json");
let diagram1 = from_str(&payload).unwrap();
let diagram2 = from_str(&payload).unwrap();
assert_eq!(diagram1.edges, diagram2.edges);
let edge_pairs: Vec<(&str, &str)> = diagram1
.edges
.iter()
.map(|edge| (edge.from.as_str(), edge.to.as_str()))
.collect();
assert_eq!(edge_pairs, vec![("A", "B"), ("C", "A"), ("A", "C")]);
}
#[test]
fn hydration_ignores_unknown_extension_namespace() {
let payload = mmds_fixture("layout-with-unknown-extension.json");
assert!(from_str(&payload).is_ok());
}
#[test]
fn routed_mmds_hydrates_node_sizes_paths_and_label_positions() {
let payload = positioned_fixture("routed-basic.json");
let geom = hydrate_routed_geometry_from_mmds(&payload).expect("routed geometry should hydrate");
assert_eq!(geom.nodes["A"].rect.width, 120.0);
assert_eq!(geom.edges[0].path.len(), 3);
assert_eq!(geom.edges[0].label_position.unwrap().x, 80.0);
assert_eq!(geom.subgraphs["sg1"].rect.width, 180.0);
}
#[test]
fn layout_geometry_level_builds_graph_geometry_without_edge_paths() {
let payload = positioned_fixture("layout-basic.json");
let geom = hydrate_graph_geometry_from_mmds(&payload).expect("layout geometry should hydrate");
assert_eq!(geom.nodes["A"].rect.width, 120.0);
assert!(geom.edges[0].layout_path_hint.is_none());
assert!(geom.edges[0].label_position.is_none());
assert!(geom.subgraphs.contains_key("sg1"));
}
#[test]
fn hydration_populates_edge_subgraph_endpoint_intent_when_present() {
let payload = mmds_fixture("subgraph-endpoint-intent-present.json");
let diagram = from_str(&payload).expect("valid hydration");
let into_subgraph = diagram
.edges
.iter()
.find(|edge| edge.from == "Client" && edge.to == "API")
.expect("client -> api edge should exist");
assert_eq!(into_subgraph.to_subgraph.as_deref(), Some("sg1"));
assert!(into_subgraph.from_subgraph.is_none());
let from_subgraph = diagram
.edges
.iter()
.find(|edge| edge.from == "DB" && edge.to == "Logs")
.expect("db -> logs edge should exist");
assert_eq!(from_subgraph.from_subgraph.as_deref(), Some("sg1"));
assert!(from_subgraph.to_subgraph.is_none());
}
#[test]
fn hydration_preserves_subgraph_endpoint_fallback_when_intent_is_omitted() {
let payload = mmds_fixture("subgraph-endpoint-intent-missing.json");
let diagram = from_str(&payload).expect("valid hydration");
assert!(
diagram
.edges
.iter()
.all(|edge| edge.from_subgraph.is_none() && edge.to_subgraph.is_none())
);
}
#[test]
fn endpoint_intent_absent_payload_uses_documented_fallback_behavior() {
let payload = mmds_fixture("subgraph-endpoint-intent-missing.json");
let diagram = from_str(&payload).expect("valid hydration");
let into_backend = diagram
.edges
.iter()
.find(|edge| edge.from == "Client" && edge.to == "API")
.expect("client -> api edge should exist");
let from_backend = diagram
.edges
.iter()
.find(|edge| edge.from == "DB" && edge.to == "Logs")
.expect("db -> logs edge should exist");
assert!(into_backend.to_subgraph.is_none());
assert!(from_backend.from_subgraph.is_none());
}
#[test]
fn hydration_accepts_unknown_extension_namespace_profiles_fixture() {
let payload = profile_fixture("unknown-extension.json");
assert!(from_str(&payload).is_ok());
}
#[test]
fn hydration_rejects_unknown_core_version_even_with_known_profiles() {
let payload = profile_fixture("unknown-core-version.json");
let err = from_str(&payload).unwrap_err();
assert!(matches!(err, HydrationError::UnsupportedVersion { .. }));
}
#[test]
fn profile_negotiation_reports_supported_and_unknown_profiles() {
let payload = profile_fixture("mixed-known-unknown.json");
let result = evaluate_profiles(&payload).unwrap();
assert!(result.supported.contains(&"mmds-core-v1".to_string()));
assert!(result.supported.contains(&"mmdflux-svg-v1".to_string()));
assert!(result.supported.contains(&"mmdflux-text-v1".to_string()));
assert!(
result
.unknown
.contains(&"vendor.experimental-v9".to_string())
);
}
#[test]
fn mmds_fixture_matrix_covers_valid_and_invalid_payloads() {
let cases = [
("layout-valid-flowchart.json", true),
("layout-valid-class.json", true),
("subgraph-endpoint-intent-present.json", true),
("subgraph-endpoint-intent-missing.json", true),
("subgraph-endpoint-subgraph-to-subgraph-present.json", true),
("subgraph-endpoint-subgraph-to-subgraph-missing.json", true),
("profiles/unknown-extension.json", true),
("invalid/dangling-edge-target.json", false),
("invalid/dangling-endpoint-intent-subgraph.json", false),
("invalid/dangling-subgraph-parent.json", false),
("invalid/invalid-shape.json", false),
("invalid/unsupported-version.json", false),
("profiles/unknown-core-version.json", false),
];
for (path, should_pass) in cases {
let payload = mmds_fixture(path);
assert_eq!(
from_str(&payload).is_ok(),
should_pass,
"fixture {} expected pass={}",
path,
should_pass
);
}
}
#[test]
fn dangling_edge_error_message_matches_docs_example() {
let payload = invalid_fixture("dangling-edge-target.json");
let err = from_str(&payload).unwrap_err();
assert_eq!(
err.to_string(),
"MMDS validation error: edge e0 target 'X' not found"
);
}
fn mmds_fixture(name: &str) -> String {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join("mmds")
.join(name);
fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()))
}
fn invalid_fixture(name: &str) -> String {
mmds_fixture(&format!("invalid/{name}"))
}
fn positioned_fixture(name: &str) -> String {
mmds_fixture(&format!("positioned/{name}"))
}
fn profile_fixture(name: &str) -> String {
mmds_fixture(&format!("profiles/{name}"))
}