use std::collections::HashMap;
use std::time::Duration;
use operonx::{Interrupt, Operon};
use serde_json::{json, Map, Value};
fn scratch_probe_graph() -> String {
json!({
"schema_version": "1.0",
"type": "graph",
"name": "main",
"full_name": "main",
"entries": ["echo"],
"exits": ["echo"],
"initial_ready_count": {"echo": 0},
"compiled_adj": {"echo": []},
"inputs": {},
"outputs": {"out": {}},
"ops": {
"echo": {
"type": "code",
"name": "echo",
"full_name": "main.echo",
"func_name": "scratch_echo",
"bound": "sync",
"inputs": {
"x": {
"required": true,
"scratch": {"key": "seeded_key"}
}
},
"outputs": {
"out": {
"ref": {
"source": "__PARENT__",
"var": "out",
"is_output": true
}
}
}
}
}
})
.to_string()
}
#[tokio::test]
async fn handle_scratch_seed_round_trips_to_op_input() {
let graph = scratch_probe_graph();
let engine = Operon::builder(&graph)
.no_resources()
.install_global_hub(false)
.load_dotenv(false)
.op("scratch_echo", |inputs: Map<String, Value>| {
Ok(json!({ "out": inputs.get("x").cloned().unwrap_or(Value::Null) }))
})
.build()
.expect("build engine");
let mut seed = Map::new();
seed.insert("seeded_key".into(), json!("hello-from-seed"));
let out = engine
.run_json_async_with_scratch(Map::new(), None, None, None, Some(seed))
.await
.expect("run");
assert_eq!(out["out"], json!("hello-from-seed"));
}
#[tokio::test]
async fn handle_scratch_arc_is_visible_post_run() {
let graph = scratch_probe_graph();
let engine = Operon::builder(&graph)
.no_resources()
.install_global_hub(false)
.load_dotenv(false)
.op("scratch_echo", |inputs: Map<String, Value>| {
Ok(json!({ "out": inputs.get("x").cloned().unwrap_or(Value::Null) }))
})
.build()
.expect("build engine");
let mut seed = Map::new();
seed.insert("seeded_key".into(), json!("seeded"));
seed.insert("other_key".into(), json!(42));
let mut handle = engine
.start(Map::new(), None, None, None, None, Some(seed))
.expect("start engine");
let _ = handle.collect(operonx::CollectMode::Group, true).await;
let scratch = handle.scratch();
let s = scratch.lock();
assert_eq!(s.get("seeded_key"), Some(&json!("seeded")));
assert_eq!(s.get("other_key"), Some(&json!(42)));
}
#[tokio::test]
async fn handle_scratch_pre_seed_via_lock_visible_to_op() {
let graph = scratch_probe_graph();
let engine = Operon::builder(&graph)
.no_resources()
.install_global_hub(false)
.load_dotenv(false)
.op("scratch_echo", |inputs: Map<String, Value>| {
Ok(json!({ "out": inputs.get("x").cloned().unwrap_or(Value::Null) }))
})
.build()
.expect("build engine");
let mut handle = engine
.start(Map::new(), None, None, None, None, None)
.expect("start engine");
{
let scratch = handle.scratch();
let mut s = scratch.lock();
s.insert("seeded_key".into(), json!("written-pre-await"));
}
let result = handle
.collect(operonx::CollectMode::Group, true)
.await
.expect("collect");
assert_eq!(result["out"], json!("written-pre-await"));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn handle_interrupts_returns_typed_list_from_synthetic_frames() {
let graph = json!({
"schema_version": "1.0",
"type": "graph",
"name": "main",
"full_name": "main",
"entries": ["slow", "kicker"],
"exits": ["slow", "kicker"],
"initial_ready_count": {"slow": 0, "kicker": 0},
"compiled_adj": {"slow": [], "kicker": []},
"inputs": {
"seed": {"required": true}
},
"outputs": {},
"ops": {
"slow": {
"type": "code",
"name": "slow",
"full_name": "main.slow",
"func_name": "long_sleep",
"is_async": true,
"bound": "io",
"inputs": {
"_seed": {
"required": true,
"ref": {"source": "__PARENT__", "var": "seed"}
}
},
"outputs": {}
},
"kicker": {
"type": "code",
"name": "kicker",
"full_name": "main.kicker",
"func_name": "kick_with_typed",
"bound": "io",
"inputs": {
"_seed": {
"required": true,
"ref": {"source": "__PARENT__", "var": "seed"}
}
},
"outputs": {}
}
}
})
.to_string();
let engine = Operon::builder(&graph)
.no_resources()
.install_global_hub(false)
.load_dotenv(false)
.op_async("long_sleep", |_inputs: Map<String, Value>| async move {
tokio::time::sleep(Duration::from_secs(5)).await;
Ok(json!({"out": "should-not-finish"}))
})
.op("kick_with_typed", |_inputs: Map<String, Value>| {
let irq = Interrupt::new(vec!["main".to_string()], "rust-typed");
Ok(irq.into())
})
.build()
.expect("build engine");
let mut inputs = Map::new();
inputs.insert("seed".into(), json!("x"));
let mut handle = engine
.start(inputs, None, None, None, None, None)
.expect("start");
let _ = tokio::time::timeout(
Duration::from_secs(2),
handle.collect(operonx::CollectMode::Group, true),
)
.await
.expect("collect");
let interrupts = handle.interrupts();
assert!(
!interrupts.is_empty(),
"expected at least one Interrupt event in the frame stream"
);
let irq = &interrupts[0];
assert_eq!(irq.ctx_to_cancel, vec!["main".to_string()]);
assert_eq!(irq.reason, "rust-typed");
}
#[test]
fn handle_scratch_type_is_hash_map_string_value() {
use parking_lot::Mutex;
use std::sync::Arc;
let _t: Arc<Mutex<HashMap<String, Value>>> = Arc::new(Mutex::new(HashMap::new()));
}