use behavior_contracts::{
assert_portable, assert_portable_component_graph, canonical_json, canonical_value,
decode_value, deep_equals, evaluate_expression, py_float_repr, render_template, run_behavior,
run_plan, validate_envelope, ComponentExec, ExecOutcome, OpSpec, Value,
};
use serde_json::json;
fn scope(pairs: &[(&str, Value)]) -> Vec<(String, Value)> {
pairs
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect()
}
#[test]
fn expr_int_add_checked() {
let r = evaluate_expression(&json!({"add": [1, 2]}), &[]).unwrap();
assert!(matches!(r, Value::Int(3)));
}
#[test]
fn expr_i64_overflow_is_failure_not_panic() {
let e = evaluate_expression(
&json!({"add": [{"int": "9223372036854775807"}, {"int": "1"}]}),
&[],
)
.unwrap_err();
assert_eq!(e.code.as_str(), "INT_OVERFLOW");
}
#[test]
fn expr_int_literal_beyond_i64_is_failure() {
let e = evaluate_expression(&json!({"int": "99999999999999999999999"}), &[]).unwrap_err();
assert_eq!(e.code.as_str(), "INT_OVERFLOW");
}
#[test]
fn expr_mod_sign_follows_dividend() {
let r = evaluate_expression(&json!({"mod": [{"int": "-7"}, {"int": "2"}]}), &[]).unwrap();
assert!(matches!(r, Value::Int(-1)), "got {r:?}");
let r2 = evaluate_expression(&json!({"mod": [{"int": "7"}, {"int": "-2"}]}), &[]).unwrap();
assert!(matches!(r2, Value::Int(1)), "got {r2:?}");
}
#[test]
fn expr_div_always_float() {
let r = evaluate_expression(&json!({"div": [6, 2]}), &[]).unwrap();
match r {
Value::Float(f) => assert_eq!(f, 3.0),
other => panic!("expected float, got {other:?}"),
}
}
#[test]
fn expr_div_by_zero_is_nan_or_inf() {
let e = evaluate_expression(&json!({"div": [1, 0]}), &[]).unwrap_err();
assert_eq!(e.code.as_str(), "NAN_OR_INF");
}
#[test]
fn expr_precision_loss_widening() {
let e =
evaluate_expression(&json!({"div": [{"int": "9007199254740993"}, 1]}), &[]).unwrap_err();
assert_eq!(e.code.as_str(), "PRECISION_LOSS");
}
#[test]
fn expr_string_compare_code_point_order() {
let r = evaluate_expression(&json!({"lt": ["Z", "a"]}), &[]).unwrap();
assert!(matches!(r, Value::Bool(true)));
}
#[test]
fn expr_ref_and_short_circuit() {
let s = scope(&[("x", Value::Obj(vec![("n".into(), Value::Int(5))]))]);
let r = evaluate_expression(&json!({"ref": ["x", "n"]}), &s).unwrap();
assert!(matches!(r, Value::Int(5)));
let r2 = evaluate_expression(&json!({"and": [false, {"ref": ["nope"]}]}), &[]).unwrap();
assert!(matches!(r2, Value::Bool(false)));
}
#[test]
fn expr_unknown_op_fail_closed() {
let e = evaluate_expression(&json!({"frobnicate": [1]}), &[]).unwrap_err();
assert_eq!(e.code.as_str(), "UNKNOWN_OP");
}
#[test]
fn template_strict_and_stringify() {
let p = scope(&[("n", Value::Int(42)), ("flag", Value::Bool(true))]);
assert_eq!(render_template("N#{n}", &p).unwrap(), "N#42");
assert_eq!(render_template("F#{flag}", &p).unwrap(), "F#True");
}
#[test]
fn template_missing_key_is_failure_not_empty() {
let e = render_template("USER#{userId}", &[]).unwrap_err();
assert_eq!(e.code.as_str(), "UNBOUND_PARAM");
}
#[test]
fn template_null_param_is_failure() {
let p = scope(&[("userId", Value::Null)]);
let e = render_template("USER#{userId}", &p).unwrap_err();
assert_eq!(e.code.as_str(), "UNBOUND_PARAM");
}
#[test]
fn template_empty_braces_are_literal() {
assert_eq!(render_template("a{}b", &[]).unwrap(), "a{}b");
}
#[test]
fn canonical_top_level_sort_only() {
let v = decode_value(&json!({"b": {"y": "1", "x": "2"}, "a": "0"})).unwrap();
assert_eq!(
canonical_value(&v).unwrap(),
"{\"a\":\"0\",\"b\":{\"y\":\"1\",\"x\":\"2\"}}"
);
}
#[test]
fn canonical_json_all_levels_sort() {
let v = decode_value(&json!({"outer": {"y": "1", "x": "2"}})).unwrap();
assert_eq!(
canonical_json(&v).unwrap(),
"{\"outer\":{\"x\":\"2\",\"y\":\"1\"}}"
);
}
#[test]
fn canonical_astral_key_sort_code_point() {
let v = decode_value(&json!({"\u{10000}": "astral", "\u{E000}": "bmp"})).unwrap();
assert_eq!(
canonical_value(&v).unwrap(),
"{\"\u{E000}\":\"bmp\",\"\u{10000}\":\"astral\"}"
);
}
#[test]
fn canonical_int_float_distinct() {
assert_eq!(canonical_json(&Value::Int(1)).unwrap(), "1");
assert_eq!(canonical_json(&Value::Float(1.0)).unwrap(), "1.0");
}
#[test]
fn canonical_nan_inf_failure() {
assert_eq!(
py_float_repr(f64::NAN).unwrap_err().code.as_str(),
"NAN_OR_INF"
);
assert_eq!(
py_float_repr(f64::INFINITY).unwrap_err().code.as_str(),
"NAN_OR_INF"
);
}
#[test]
fn py_float_repr_matches_cpython() {
let cases: &[(f64, &str)] = &[
(0.1, "0.1"),
(1e16, "1e+16"),
(-0.0, "-0.0"),
(0.0, "0.0"),
(9999000000000000.0, "9999000000000000.0"),
(5e-324, "5e-324"),
(1.7976931348623157e308, "1.7976931348623157e+308"),
(1.0, "1.0"),
(1e-5, "1e-05"),
(0.0001, "0.0001"),
(3.5048, "3.5048"),
(1.25e30, "1.25e+30"),
];
for (f, want) in cases {
assert_eq!(py_float_repr(*f).unwrap(), *want, "py_float_repr({f})");
}
}
#[test]
fn plan_null_binding_skip_propagates() {
let ops = vec![
OpSpec {
id: "root".into(),
parent: None,
bind_field: None,
relation_kind: None,
policy: None,
},
OpSpec {
id: "child".into(),
parent: Some(0),
bind_field: Some("next".into()),
relation_kind: None,
policy: None,
},
];
let res = run_plan(None, &ops, |op, _| {
if op.id == "root" {
ExecOutcome::Ok(Value::Obj(vec![("x".into(), Value::Int(1))]))
} else {
ExecOutcome::Ok(Value::Null)
}
})
.unwrap();
assert_eq!(res.executed, vec!["root".to_string()]);
assert_eq!(res.skipped, vec!["child".to_string()]);
}
#[test]
fn plan_fail_policy_raises() {
let ops = vec![OpSpec {
id: "a".into(),
parent: None,
bind_field: None,
relation_kind: None,
policy: None,
}];
let e = run_plan(None, &ops, |_, _| ExecOutcome::Error("boom".into())).unwrap_err();
assert_eq!(e.code.as_str(), "OP_FAILED");
}
#[test]
fn plan_unknown_policy_fail_closed() {
let ops = vec![OpSpec {
id: "a".into(),
parent: None,
bind_field: None,
relation_kind: None,
policy: Some("wat".into()),
}];
let e = run_plan(None, &ops, |_, _| ExecOutcome::Ok(Value::Null)).unwrap_err();
assert_eq!(e.code.as_str(), "UNKNOWN_POLICY");
}
#[test]
fn envelope_version_checks() {
assert!(validate_envelope(&json!({"specVersion": "1.0"}), "1.1", None, None).is_ok());
assert!(validate_envelope(&json!({"specVersion": "1.1"}), "1.1", None, None).is_ok());
assert_eq!(
validate_envelope(&json!({"specVersion": "2.0"}), "1.1", None, None)
.unwrap_err()
.code
.as_str(),
"UNSUPPORTED_MAJOR"
);
assert_eq!(
validate_envelope(&json!({"specVersion": "1.2"}), "1.1", None, None)
.unwrap_err()
.code
.as_str(),
"UNSUPPORTED_MINOR"
);
assert_eq!(
validate_envelope(&json!({}), "1.1", None, None)
.unwrap_err()
.code
.as_str(),
"MISSING_VERSION"
);
assert_eq!(
validate_envelope(&json!({"specVersion": "x"}), "1.1", None, None)
.unwrap_err()
.code
.as_str(),
"MALFORMED_VERSION"
);
}
#[test]
fn guard_rejects_non_finite() {
assert!(assert_portable(&Value::Obj(vec![("x".into(), Value::Float(f64::NAN))])).is_err());
assert!(assert_portable(&Value::Arr(vec![Value::Int(1), Value::Str("ok".into())])).is_ok());
}
#[test]
fn codec_int_float_distinction() {
let a = decode_value(&json!(1)).unwrap();
let b = decode_value(&json!({"float": 1})).unwrap();
assert!(matches!(a, Value::Int(1)));
assert!(matches!(b, Value::Float(_)));
assert!(!deep_equals(&a, &b), "int 1 must not equal float 1.0");
}
struct OneShot {
ok: Value,
}
impl ComponentExec for OneShot {
fn exec(
&mut self,
component: &str,
ports: &[(String, Value)],
_bound: Option<&Value>,
) -> Option<ExecOutcome> {
if component != "GetItem" {
return None; }
let pk = ports
.iter()
.find(|(k, _)| k == "PK")
.map(|(_, v)| v.clone())
.unwrap_or(Value::Null);
let _ = &self.ok;
Some(ExecOutcome::Ok(Value::Obj(vec![("echoedPK".into(), pk)])))
}
}
#[test]
fn behavior_component_ref_wires_port_from_input() {
let ir = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{
"name": "get", "inputPorts": {"id": {"type": "string", "required": true}},
"body": [{"id": "a", "component": "GetItem",
"ports": {"PK": {"concat": ["ID#", {"ref": ["id"]}]}}}],
"output": {"ref": ["a"]},
"plan": {"groups": [[0]], "concurrency": 1}
}]
});
let mut h = OneShot { ok: Value::Null };
let out = run_behavior(&ir, &mut h, &scope(&[("id", Value::Str("7".into()))]), None).unwrap();
assert!(deep_equals(
&out,
&Value::Obj(vec![("echoedPK".into(), Value::Str("ID#7".into()))])
));
}
#[test]
fn behavior_unknown_component_fails_closed() {
let ir = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {},
"body": [{"id": "a", "component": "Nope", "ports": {}}], "output": {"ref": ["a"]}}]
});
let mut h = OneShot { ok: Value::Null };
let err = run_behavior(&ir, &mut h, &[], None).unwrap_err();
assert_eq!(err.code(), "UNKNOWN_COMPONENT");
}
#[test]
fn behavior_guard_rejects_non_string_component_and_unknown_op() {
let bad_component = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {},
"body": [{"id": "a", "component": ["not", "a", "string"], "ports": {}}], "output": null}]
});
assert!(assert_portable_component_graph(&bad_component).is_err());
let unknown_op = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {},
"body": [{"id": "a", "component": "GetItem", "ports": {"PK": {"bogusOp": [1, 2]}}}],
"output": null}]
});
assert!(assert_portable_component_graph(&unknown_op).is_err());
let good = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "g", "inputPorts": {},
"body": [{"id": "a", "component": "GetItem",
"ports": {"PK": {"concat": ["ARTICLE#", {"ref": ["articleId"]}]}}}],
"output": {"ref": ["a"]}}]
});
assert!(assert_portable_component_graph(&good).is_ok());
}
#[test]
fn behavior_guard_obj_arg_keys_are_data_not_operators() {
let single_key_obj = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {}, "body": [],
"output": {"obj": {"posts": {"ref": ["a"]}}}}]
});
assert!(assert_portable_component_graph(&single_key_obj).is_ok());
let colliding_key = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {}, "body": [],
"output": {"obj": {"add": 1}}}]
});
assert!(assert_portable_component_graph(&colliding_key).is_ok());
let bad_value = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {}, "body": [],
"output": {"obj": {"k": {"bogusOp": [1]}}}}]
});
assert!(assert_portable_component_graph(&bad_value).is_err());
}
#[test]
fn behavior_guard_rejects_static_proto_obj_key() {
let bad = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {}, "body": [],
"output": {"obj": {"__proto__": 1}}}]
});
let err = assert_portable_component_graph(&bad).unwrap_err();
assert!(err.message.contains("__proto__"), "got: {}", err.message);
let bad_nested = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {},
"body": [{"id": "a", "component": "GetItem",
"ports": {"PK": {"obj": {"__proto__": {"ref": ["x"]}}}}}],
"output": null}]
});
assert!(assert_portable_component_graph(&bad_nested).is_err());
}
#[test]
fn behavior_handler_ctx_carries_node_identity() {
struct CtxEcho;
impl ComponentExec for CtxEcho {
fn exec(
&mut self,
component: &str,
ports: &[(String, Value)],
bound: Option<&Value>,
) -> Option<ExecOutcome> {
self.exec_ctx("", component, ports, bound)
}
fn exec_ctx(
&mut self,
node_id: &str,
component: &str,
_ports: &[(String, Value)],
_bound: Option<&Value>,
) -> Option<ExecOutcome> {
Some(ExecOutcome::Ok(Value::Obj(vec![
("nodeId".into(), Value::Str(node_id.to_string())),
("component".into(), Value::Str(component.to_string())),
])))
}
}
let ir = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {},
"body": [{"id": "a", "component": "GetItem", "ports": {}}], "output": {"ref": ["a"]}}]
});
let mut h = CtxEcho;
let out = run_behavior(&ir, &mut h, &[], None).unwrap();
assert!(deep_equals(
&out,
&Value::Obj(vec![
("nodeId".into(), Value::Str("a".into())),
("component".into(), Value::Str("GetItem".into())),
])
));
}
#[test]
fn behavior_guard_accepts_v2_map_fields_and_fail_closes() {
let good = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {},
"body": [{"id": "b", "map": {
"over": {"ref": ["xs"]}, "as": "$t", "component": "BatchGetItem",
"when": {"ne": [{"ref": ["$t", "role"]}, "none"]},
"into": "tag", "batched": true,
"ports": {"PK": {"ref": ["$t", "id"]}}}}],
"output": {"ref": ["b"]}}]
});
assert!(assert_portable_component_graph(&good).is_ok());
let bad_when = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {},
"body": [{"id": "b", "map": {
"over": {"ref": ["xs"]}, "as": "$t", "component": "BatchGetItem",
"when": {"bogusOp": [1]},
"ports": {}}}],
"output": null}]
});
assert!(assert_portable_component_graph(&bad_when).is_err());
let bad_into = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {},
"body": [{"id": "b", "map": {
"over": {"ref": ["xs"]}, "as": "$t", "component": "BatchGetItem",
"into": 42,
"ports": {}}}],
"output": null}]
});
assert!(assert_portable_component_graph(&bad_into).is_err());
let bad_batched = json!({
"irVersion": 1, "exprVersion": 2,
"components": [{"name": "x", "inputPorts": {},
"body": [{"id": "b", "map": {
"over": {"ref": ["xs"]}, "as": "$t", "component": "BatchGetItem",
"batched": "yes",
"ports": {}}}],
"output": null}]
});
assert!(assert_portable_component_graph(&bad_batched).is_err());
}