#![cfg(feature = "repl")]
use std::sync::Arc;
use tinyagents::harness::providers::MockModel;
use tinyagents::registry::CapabilityRegistry;
use tinyagents::{ReplCapabilities, ReplPolicy, ReplSession, TinyAgentsError};
const GRAPH_SOURCE: &str = r#"graph triage {
start classify
node classify {
kind model
model "assistant"
next END
}
}"#;
fn session_with_assistant(policy: ReplPolicy) -> ReplSession {
let mut registry = CapabilityRegistry::<()>::new();
registry
.register_model("assistant", Arc::new(MockModel::constant("ok")))
.expect("register model");
ReplSession::<()>::new()
.with_policy(policy)
.with_capabilities(ReplCapabilities::new(Arc::new(registry)))
}
fn define_cell() -> String {
format!(r#"graph_define(#{{ name: "triage", source: `{GRAPH_SOURCE}` }})"#)
}
#[test]
fn graph_define_drafts_an_uncompiled_review_gated_blueprint() {
let mut s = session_with_assistant(ReplPolicy::default());
let result = s.eval_cell(&define_cell()).expect("graph_define drafts");
let descriptor = result.value.expect("descriptor map").to_json();
assert_eq!(descriptor["name"], serde_json::json!("triage"));
assert_eq!(descriptor["nodes"], serde_json::json!(1));
assert_eq!(descriptor["compiled"], serde_json::json!(false));
assert_eq!(descriptor["requires_review"], serde_json::json!(true));
}
#[test]
fn graph_validate_reports_unresolved_capability_references() {
let mut s = ReplSession::<()>::new();
let script = format!(
r#"let g = graph_define(#{{ name: "triage", source: `{GRAPH_SOURCE}` }});
graph_validate(g)"#
);
let result = s.eval_cell(&script).expect("define + validate");
let messages = result.value.expect("array of messages").to_json();
let messages = messages.as_array().expect("array");
assert!(
messages
.iter()
.any(|m| m.as_str().is_some_and(|s| s.contains("assistant"))),
"validation should flag the unresolved `assistant` reference, got {messages:?}"
);
}
#[test]
fn full_lifecycle_compiles_then_registers_with_a_review_token() {
let mut s = session_with_assistant(ReplPolicy::default());
let compiled = s
.eval_cell(&format!(
r#"let g = graph_define(#{{ name: "triage", source: `{GRAPH_SOURCE}` }});
graph_compile(g)"#
))
.expect("define + compile");
let descriptor = compiled.value.expect("compiled descriptor").to_json();
assert_eq!(descriptor["compiled"], serde_json::json!(true));
assert_eq!(descriptor["requires_review"], serde_json::json!(true));
let err = s
.eval_cell(
r#"let g = graph_compile(#{ name: "triage" });
graph_register(#{ graph: g })"#,
)
.expect_err("registration without review must fail");
assert!(
matches!(err, TinyAgentsError::Validation(ref m) if m.contains("review")),
"got {err:?}"
);
let registered = s
.eval_cell(
r#"let g = graph_compile(#{ name: "triage" });
graph_register(#{ graph: g, review_id: "reviewed-by-human" })"#,
)
.expect("registration with review token succeeds");
assert_eq!(
registered.value.expect("name").to_json(),
serde_json::json!("triage")
);
}
#[test]
fn register_before_compile_is_rejected() {
let mut s = session_with_assistant(ReplPolicy::default());
let err = s
.eval_cell(&format!(
r#"let g = graph_define(#{{ name: "triage", source: `{GRAPH_SOURCE}` }});
graph_register(#{{ graph: g }})"#
))
.expect_err("registering an uncompiled draft must fail");
assert!(
matches!(err, TinyAgentsError::Validation(ref m) if m.contains("compiled")),
"got {err:?}"
);
}
#[test]
fn review_gate_can_be_disabled_by_policy() {
let policy = ReplPolicy {
generated_graphs_require_review: false,
..ReplPolicy::default()
};
let mut s = session_with_assistant(policy);
let registered = s
.eval_cell(&format!(
r#"let g = graph_define(#{{ name: "triage", source: `{GRAPH_SOURCE}` }});
let c = graph_compile(g);
graph_register(#{{ graph: c }})"#
))
.expect("no-review policy registers without a token");
assert_eq!(
registered.value.expect("name").to_json(),
serde_json::json!("triage")
);
}