From 4a3ae05db2bc4a5fc0500e351a804aba85547fff Mon Sep 17 00:00:00 2001 From: Brummel Date: Tue, 30 Jun 2026 16:03:29 +0200 Subject: [PATCH] test(graph_model): cover a producer backing >1 re-exported output field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #47 — close a coverage gap in the graph model serializer: a single interior producer node backing more than one re-exported output field was never exercised by a fixture (every existing one maps each output to a distinct producer or a single field). The issue was filed at cycle 0022 against `aura-cli/src/graph.rs`'s `render_definition` / `output_binding` helper, which rendered a textual `(n1, n2) := ` tuple binding. That file and helper no longer exist — the textual definition renderer was replaced by the JSON model emission now in `aura-engine/src/graph_model.rs`, where the multi-OutField case is plain flat iteration over `c.output()` (one `[name,kind]` entry + one `#N` boundary binding per `OutField`, by `(node, field)`), not a special tuple-binding arm. So the original branch is gone; this guards the flat emission that replaced it. New test `multi_outfield_producer_emits_one_binding_per_field` (+ two fixtures): a two-field producer (`hi`:f64, `lo`:i64 — distinct kinds, so the per-field `field_kind` lookup is exercised) re-exported as both composite outputs, nested in a root so it renders through `composite_def_json`. Asserts both fields surface with their own kind (`"outputs":[["hi","f64"],["lo","i64"]]`) and both bindings reference the same producer at distinct ordinals (`0.o0`/`#0`, `0.o1`/`#1`). Test-only; no production code touched (the behaviour was already correct). Verified: cargo test -p aura-engine green (237 in the lib suite); clippy clean. closes #47 --- crates/aura-engine/src/graph_model.rs | 61 +++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/crates/aura-engine/src/graph_model.rs b/crates/aura-engine/src/graph_model.rs index 9cb1890..1127356 100644 --- a/crates/aura-engine/src/graph_model.rs +++ b/crates/aura-engine/src/graph_model.rs @@ -399,6 +399,49 @@ mod tests { ) } + /// A primitive producer whose output record carries TWO fields of DIFFERENT + /// kinds (`hi`: f64, `lo`: i64) — the smallest interior node that can back more + /// than one re-exported composite output field, with distinct per-field kinds so + /// the `field_kind` lookup is meaningfully exercised (not two identical kinds). + fn two_field_producer() -> PrimitiveBuilder { + PrimitiveBuilder::new( + "Split", + NodeSchema { + inputs: vec![], + output: vec![ + FieldSpec { name: "hi".into(), kind: ScalarKind::F64 }, + FieldSpec { name: "lo".into(), kind: ScalarKind::I64 }, + ], + params: vec![], + }, + |_| Box::new(Bare), + ) + } + + /// A root harness whose sole interior node is a composite (`splitter`) that + /// re-exports BOTH fields of one two-field producer (node 0). Nested in a root + /// so `model_to_json` renders `splitter` through the `composite_def_json` path + /// (which is what emits the `outputs` list and the `#N` boundary bindings). + fn multi_outfield_root() -> Composite { + let splitter = Composite::new( + "splitter", + vec![two_field_producer().into()], + vec![], + vec![], + vec![ + OutField { node: 0, field: 0, name: "hi".into() }, + OutField { node: 0, field: 1, name: "lo".into() }, + ], + ); + Composite::new( + "root", + vec![BlueprintNode::Composite(splitter)], + vec![], + vec![], + vec![], + ) + } + // -- Task 2: primitive record ------------------------------------------------ #[test] @@ -518,6 +561,24 @@ mod tests { assert!(def.starts_with(r#"{"inputs":[["f64","any","a"],["f64","any","b"]],"#), "{def}"); } + /// Property: a single interior producer node that backs MORE THAN ONE + /// re-exported output field is serialized as one boundary binding per field — + /// each `outputs` entry carries that field's OWN kind (`hi`→f64, `lo`→i64, not a + /// shared kind), and each `#N` binding endpoint references the SAME producer node + /// at a DISTINCT field ordinal (`o0` vs `o1`). Guards the flat per-`OutField` + /// model emission (`composite_def_json`) that replaced the retired textual + /// `(n1, n2) := ` tuple-binding renderer (#47). + #[test] + fn multi_outfield_producer_emits_one_binding_per_field() { + let model = model_to_json(&multi_outfield_root()); + // both fields surface, each with its OWN per-field kind (f64 vs i64): + assert!(model.contains(r#""outputs":[["hi","f64"],["lo","i64"]]"#), "{model}"); + // both boundary bindings reference the SAME producer node (0) at distinct + // field ordinals — o0 → #0 and o1 → #1: + assert!(model.contains(r##"["0.o0","#0"]"##), "{model}"); + assert!(model.contains(r##"["0.o1","#1"]"##), "{model}"); + } + #[test] fn structural_miswire_changes_the_model() { let ok = model_to_json(&sample_root());