jellyflow-core 0.2.0

Headless graph model, IDs, type descriptors, and interaction policy primitives for Jellyflow.
Documentation
use super::*;

#[test]
fn apply_transaction_rejects_node_with_missing_ports_atomically() {
    let mut graph = Graph::default();
    let before = graph.clone();

    let node_id = NodeId::new();
    let missing_port = PortId::new();
    let mut node = make_node("core.a");
    node.ports.push(missing_port);

    let tx = GraphTransaction::from_ops([GraphOp::AddNode { id: node_id, node }]);

    let err = apply_transaction(&mut graph, &tx).expect_err("invalid transaction must fail");
    assert!(matches!(err, ApplyError::InvalidTransactionResult { .. }));
    assert_eq!(
        serde_json::to_value(&graph).unwrap(),
        serde_json::to_value(&before).unwrap()
    );
}

#[test]
fn apply_transaction_rejects_unordered_added_port_atomically() {
    let mut graph = Graph::default();
    let node_id = NodeId::new();
    graph.nodes.insert(node_id, make_node("core.a"));
    let before = graph.clone();

    let port_id = PortId::new();
    let tx = GraphTransaction::from_ops([GraphOp::AddPort {
        id: port_id,
        port: make_port(node_id, "out", PortDirection::Out),
    }]);

    let err = apply_transaction(&mut graph, &tx).expect_err("invalid transaction must fail");
    assert!(matches!(err, ApplyError::InvalidTransactionResult { .. }));
    assert_eq!(
        serde_json::to_value(&graph).unwrap(),
        serde_json::to_value(&before).unwrap()
    );
}

#[test]
fn graph_transaction_facade_diff_apply_and_inverse_roundtrip() {
    let from = Graph::default();

    let node_id = NodeId::from_u128(10);
    let port_id = PortId::from_u128(11);
    let mut node = make_node("core.a");
    node.ports.push(port_id);

    let mut to = from.clone();
    to.nodes.insert(node_id, node);
    to.ports
        .insert(port_id, make_port(node_id, "out", PortDirection::Out));

    let tx = GraphTransaction::diff(&from, &to);
    let mut patched = from.clone();
    tx.apply_to(&mut patched).expect("apply diff facade");
    assert_eq!(
        serde_json::to_value(&patched).unwrap(),
        serde_json::to_value(&to).unwrap()
    );

    tx.inverse()
        .apply_to(&mut patched)
        .expect("apply inverse facade");
    assert_eq!(
        serde_json::to_value(&patched).unwrap(),
        serde_json::to_value(&from).unwrap()
    );
}

#[test]
fn graph_transaction_facade_consumes_ops_and_parts() {
    let node_id = NodeId::new();
    let tx = GraphTransaction::from_ops([GraphOp::SetNodeHidden {
        id: node_id,
        from: false,
        to: true,
    }])
    .with_label("Hide node");

    let (label, ops) = tx.clone().into_parts();
    assert_eq!(label.as_deref(), Some("Hide node"));
    assert!(matches!(
        ops.as_slice(),
        [GraphOp::SetNodeHidden {
            id,
            from: false,
            to: true
        }] if *id == node_id
    ));

    let rebuilt = GraphTransaction::from_parts(label, ops.clone());
    assert_eq!(rebuilt.label(), Some("Hide node"));
    assert_eq!(rebuilt.ops().len(), 1);

    let ops = tx.into_ops();
    assert_eq!(ops.len(), 1);
}