Files
Entropyk/crates/entropyk/tests/port_validated_edges.rs
sepehr 3358b74342 Add diagram workbench UI with Modelica DoF coaching and ISO glyphs.
Ship the Next.js cycle editor with CAD chrome, technical HX symbols, Fixed/Free boundary guidance, and secondary water/air pressure drop support in the solver stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 22:46:46 +02:00

209 lines
5.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Integration tests for SystemBuilder::edge_with_ports()
use entropyk::SystemBuilder;
use entropyk_components::{ComponentError, ConnectedPort, JacobianBuilder, ResidualVector};
/// A minimal mock component with configurable ports for testing.
struct MockComponentWithPorts {
n_eqs: usize,
ports: Vec<ConnectedPort>,
}
impl MockComponentWithPorts {
fn new(n_eqs: usize) -> Self {
Self {
n_eqs,
ports: Vec::new(),
}
}
#[allow(dead_code)]
fn with_ports(n_eqs: usize, ports: Vec<ConnectedPort>) -> Self {
Self { n_eqs, ports }
}
}
impl entropyk_components::Component for MockComponentWithPorts {
fn compute_residuals(
&self,
_state: &[f64],
_residuals: &mut ResidualVector,
) -> Result<(), ComponentError> {
Ok(())
}
fn jacobian_entries(
&self,
_state: &[f64],
_jacobian: &mut JacobianBuilder,
) -> Result<(), ComponentError> {
Ok(())
}
fn n_equations(&self) -> usize {
self.n_eqs
}
fn get_ports(&self) -> &[ConnectedPort] {
&self.ports
}
}
#[test]
fn test_edge_with_ports_component_not_found() {
let result = SystemBuilder::new()
.component("a", Box::new(MockComponentWithPorts::new(1)))
.unwrap()
.edge_with_ports("missing", "outlet", "a", "inlet");
assert!(result.is_err());
if let Err(ref err) = result {
assert!(
err.to_string().contains("not found"),
"Expected ComponentNotFound error, got: {}",
err
);
}
}
#[test]
fn test_edge_with_ports_target_not_found() {
let result = SystemBuilder::new()
.component("a", Box::new(MockComponentWithPorts::new(1)))
.unwrap()
.edge_with_ports("a", "outlet", "missing", "inlet");
assert!(result.is_err());
if let Err(ref err) = result {
assert!(
err.to_string().contains("not found"),
"Expected ComponentNotFound error, got: {}",
err
);
}
}
#[test]
fn test_edge_with_ports_cross_circuit_rejected() {
use entropyk_core::CircuitId;
let result = SystemBuilder::new()
.component_in_circuit(
"a",
Box::new(MockComponentWithPorts::new(1)),
CircuitId::ZERO,
)
.unwrap()
.component_in_circuit("b", Box::new(MockComponentWithPorts::new(1)), CircuitId(1))
.unwrap()
.edge_with_ports("a", "outlet", "b", "inlet");
assert!(result.is_err());
if let Err(ref err) = result {
assert!(
err.to_string().contains("different circuits"),
"Expected CrossCircuitEdge error, got: {}",
err
);
}
}
#[test]
fn test_edge_with_ports_convention_based_names() {
let builder = SystemBuilder::new()
.component("a", Box::new(MockComponentWithPorts::new(2)))
.unwrap()
.component("b", Box::new(MockComponentWithPorts::new(2)))
.unwrap()
.edge_with_ports("a", "outlet", "b", "inlet")
.expect("edge_with_ports should resolve convention-based port names");
assert_eq!(builder.edge_count(), 1);
}
#[test]
fn test_edge_with_ports_suction_discharge_names() {
let builder = SystemBuilder::new()
.component("comp", Box::new(MockComponentWithPorts::new(2)))
.unwrap()
.component("pipe", Box::new(MockComponentWithPorts::new(2)))
.unwrap()
.edge_with_ports("comp", "discharge", "pipe", "inlet")
.expect("edge_with_ports should resolve suction/discharge convention names");
assert_eq!(builder.edge_count(), 1);
}
#[test]
fn test_edge_with_ports_unknown_port_name_error() {
use entropyk::SystemBuilderError;
let result = SystemBuilder::new()
.component("a", Box::new(MockComponentWithPorts::new(2)))
.unwrap()
.component("b", Box::new(MockComponentWithPorts::new(2)))
.unwrap()
.edge_with_ports("a", "bogus_port", "b", "inlet");
assert!(result.is_err());
if let Err(SystemBuilderError::PortNotFound {
component,
port_name,
}) = result
{
assert_eq!(component, "a");
assert!(
port_name.starts_with("bogus_port"),
"port_name should start with the port name, got: {port_name}"
);
} else {
panic!("Expected PortNotFound error");
}
}
#[test]
fn test_edge_with_ports_same_circuit_succeeds() {
use entropyk_core::CircuitId;
let builder = SystemBuilder::new()
.component_in_circuit(
"a",
Box::new(MockComponentWithPorts::new(2)),
CircuitId::ZERO,
)
.unwrap()
.component_in_circuit(
"b",
Box::new(MockComponentWithPorts::new(2)),
CircuitId::ZERO,
)
.unwrap()
.edge_with_ports("a", "outlet", "b", "inlet")
.expect("edge_with_ports should succeed for same-circuit components");
assert_eq!(builder.edge_count(), 1);
}
#[test]
fn test_build_system_with_port_validated_edges() {
// DoF ledger post-CM1.4: 2-edge chain → 1 branch ṁ + 2×(P,h) = 5 unknowns,
// so component equations must total 5 for the finalize DoF gate (2+2+1).
// The last mock contributes a single equation to keep the system square.
let system = SystemBuilder::new()
.component("a", Box::new(MockComponentWithPorts::new(2)))
.unwrap()
.component("b", Box::new(MockComponentWithPorts::new(2)))
.unwrap()
.component("c", Box::new(MockComponentWithPorts::new(1)))
.unwrap()
.edge_with_ports("a", "outlet", "b", "inlet")
.unwrap()
.edge_with_ports("b", "outlet", "c", "inlet")
.unwrap()
.build()
.expect("build should succeed");
assert_eq!(system.node_count(), 3);
assert_eq!(system.edge_count(), 2);
}