use std::path::{Path, PathBuf};
use ncp_runtime::result::BrickResult;
use ncp_runtime::trace::NullTrace;
use ncp_runtime::{ExecuteHooks, ExecuteOptions, ExecutionReport, RuntimeContext};
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("CARGO_MANIFEST_DIR has no parent — runtime/ must live under repo root")
.to_path_buf()
}
fn run_graph(graph_rel: &str, input_rel: &str) -> ExecutionReport {
let root = repo_root();
let graph_path = root.join(graph_rel);
let input_path = root.join(input_rel);
let ctx = RuntimeContext::load(&graph_path, &root.join("examples/bricks"), None)
.unwrap_or_else(|e| panic!("RuntimeContext::load({}) failed: {e:#}", graph_rel));
let input_text = std::fs::read_to_string(&input_path)
.unwrap_or_else(|e| panic!("read_to_string({}) failed: {e}", input_rel));
let input: serde_json::Value = serde_json::from_str(&input_text)
.unwrap_or_else(|e| panic!("parse JSON {} failed: {e}", input_rel));
let mut tracer = NullTrace;
let mut hooks = ExecuteHooks::default();
let opts = ExecuteOptions::default();
ctx.execute(&input, &mut tracer, &mut hooks, &opts)
.unwrap_or_else(|e| panic!("execute({}) failed: {e:#}", graph_rel))
}
#[test]
fn smoke_echo_pipeline() {
let report = run_graph(
"examples/graphs/echo-pipeline/graph.yaml",
"examples/graphs/echo-pipeline/sample.json",
);
assert_eq!(
report.counts.failure, 0,
"echo-pipeline must not produce Failure"
);
assert!(
report.counts.success >= 1,
"echo-pipeline must produce ≥1 Success terminal; got success={}, low_confidence={}, failure={}",
report.counts.success, report.counts.low_confidence, report.counts.failure
);
assert!(
!report.terminals.is_empty(),
"expected at least one terminal"
);
}
#[test]
fn smoke_echo_chain() {
let report = run_graph(
"examples/graphs/echo-chain/graph.yaml",
"examples/graphs/echo-chain/sample.json",
);
assert_eq!(
report.counts.failure, 0,
"echo-chain must not produce Failure"
);
assert!(
report.counts.success >= 1,
"echo-chain must produce ≥1 Success terminal; got success={}, low_confidence={}, failure={}",
report.counts.success, report.counts.low_confidence, report.counts.failure
);
}
#[test]
fn smoke_support_routing_stubbed_positive() {
let report = run_graph(
"examples/graphs/support-routing-stubbed/graph.yaml",
"examples/graphs/support-routing-stubbed/sample-positive.json",
);
assert_eq!(
report.counts.failure, 0,
"positive path must not produce Failure"
);
assert!(report.counts.success >= 1);
assert!(!report.terminals.is_empty());
assert!(
report
.terminals
.iter()
.any(|t| t.brick_id.contains("classifier")),
"positive path should terminate at the classifier brick; terminals={:?}",
report
.terminals
.iter()
.map(|t| &t.brick_id)
.collect::<Vec<_>>()
);
}
#[test]
fn smoke_support_routing_stubbed_escalation() {
let report = run_graph(
"examples/graphs/support-routing-stubbed/graph.yaml",
"examples/graphs/support-routing-stubbed/sample.json",
);
assert_eq!(
report.counts.failure, 0,
"escalation path must not produce Failure (echo always succeeds)"
);
assert!(report.counts.success >= 1);
assert!(!report.terminals.is_empty());
assert!(
report.terminals.iter().any(|t| t.brick_id.contains("echo")),
"escalation path should terminate at the echo brick; terminals={:?}",
report
.terminals
.iter()
.map(|t| &t.brick_id)
.collect::<Vec<_>>()
);
}
#[test]
fn smoke_trap_pipeline_fails_as_designed() {
let report = run_graph(
"examples/graphs/trap-pipeline/graph.yaml",
"examples/graphs/trap-pipeline/sample.json",
);
assert_eq!(
report.counts.success, 0,
"trap-pipeline must not produce Success"
);
assert!(
report.counts.failure >= 1,
"trap-pipeline must produce ≥1 Failure terminal; got success={}, low_confidence={}, failure={}",
report.counts.success, report.counts.low_confidence, report.counts.failure
);
assert!(!report.terminals.is_empty());
let failure = report
.terminals
.iter()
.find(|t| matches!(&t.result, BrickResult::Failure { .. }))
.expect("expected at least one Failure terminal");
match &failure.result {
BrickResult::Failure { error } => assert!(
!error.error_class.is_empty(),
"Failure terminal must carry a non-empty error_class"
),
_ => unreachable!(),
}
}