jellyflow-runtime 0.2.0

Headless store, rules, schema, profile, and change pipeline for Jellyflow.
Documentation
use super::fixtures::{insert_data_input, insert_data_output, insert_edge, insert_node};

use crate::io::NodeGraphInteractionState;
use crate::rules::{
    plan_delete_edge, plan_delete_elements_with_policy, plan_delete_node_with_policy,
};
use jellyflow_core::core::{EdgeId, Graph, NodeId, PortCapacity, PortId};
use jellyflow_core::ops::{GraphOp, GraphTransaction};

#[test]
fn plan_delete_node_respects_policy_and_cascades_incident_edges() {
    let mut graph = Graph::default();

    let a = NodeId::new();
    let b = NodeId::new();
    insert_node(&mut graph, a, "core.a");
    insert_node(&mut graph, b, "core.b");

    let out = PortId::new();
    let inn = PortId::new();
    insert_data_output(&mut graph, out, a, "out", PortCapacity::Multi);
    insert_data_input(&mut graph, inn, b, "in", PortCapacity::Single);

    let edge_id = EdgeId::new();
    insert_edge(&mut graph, edge_id, out, inn);

    let disabled = NodeGraphInteractionState {
        nodes_deletable: false,
        edges_deletable: false,
        ..NodeGraphInteractionState::default()
    };
    let rejected = plan_delete_node_with_policy(&graph, a, &disabled);
    assert!(rejected.is_reject());
    assert!(rejected.ops().is_empty());

    graph.nodes.get_mut(&a).unwrap().deletable = Some(true);
    let accepted = plan_delete_node_with_policy(&graph, a, &disabled);
    assert!(accepted.is_accept());
    assert_eq!(accepted.ops().len(), 1);
    assert!(matches!(
        &accepted.ops()[0],
        GraphOp::RemoveNode { id, edges, .. }
            if *id == a && edges.iter().any(|(id, _)| *id == edge_id)
    ));

    GraphTransaction::from_ops(accepted.into_ops())
        .apply_to(&mut graph)
        .unwrap();

    assert!(!graph.nodes.contains_key(&a));
    assert!(graph.nodes.contains_key(&b));
    assert!(!graph.ports.contains_key(&out));
    assert!(graph.ports.contains_key(&inn));
    assert!(!graph.edges.contains_key(&edge_id));
}

#[test]
fn plan_delete_edge_respects_policy_overrides() {
    let mut graph = Graph::default();

    let a = NodeId::new();
    let b = NodeId::new();
    insert_node(&mut graph, a, "core.a");
    insert_node(&mut graph, b, "core.b");

    let out = PortId::new();
    let inn = PortId::new();
    insert_data_output(&mut graph, out, a, "out", PortCapacity::Multi);
    insert_data_input(&mut graph, inn, b, "in", PortCapacity::Single);

    let edge_id = EdgeId::new();
    insert_edge(&mut graph, edge_id, out, inn);

    let disabled = NodeGraphInteractionState {
        edges_deletable: false,
        ..NodeGraphInteractionState::default()
    };
    let default_plan = plan_delete_edge(&graph, edge_id);
    assert!(default_plan.is_accept());

    let rejected = plan_delete_elements_with_policy(&graph, [], [edge_id], &disabled);
    assert!(rejected.is_reject());
    assert!(rejected.ops().is_empty());

    graph.edges.get_mut(&edge_id).unwrap().deletable = Some(true);
    let accepted = plan_delete_elements_with_policy(&graph, [], [edge_id], &disabled);
    assert!(accepted.is_accept());
    assert_eq!(accepted.ops().len(), 1);

    GraphTransaction::from_ops(accepted.into_ops())
        .apply_to(&mut graph)
        .unwrap();

    assert!(!graph.edges.contains_key(&edge_id));
}

#[test]
fn plan_delete_elements_dedupes_node_cascaded_edges() {
    let mut graph = Graph::default();

    let a = NodeId::new();
    let b = NodeId::new();
    insert_node(&mut graph, a, "core.a");
    insert_node(&mut graph, b, "core.b");

    let out = PortId::new();
    let inn = PortId::new();
    insert_data_output(&mut graph, out, a, "out", PortCapacity::Multi);
    insert_data_input(&mut graph, inn, b, "in", PortCapacity::Single);

    let edge_id = EdgeId::new();
    insert_edge(&mut graph, edge_id, out, inn);
    graph.nodes.get_mut(&a).unwrap().deletable = Some(true);
    graph.nodes.get_mut(&b).unwrap().deletable = Some(true);
    graph.edges.get_mut(&edge_id).unwrap().deletable = Some(false);

    let disabled = NodeGraphInteractionState {
        nodes_deletable: false,
        edges_deletable: false,
        ..NodeGraphInteractionState::default()
    };
    let plan = plan_delete_elements_with_policy(&graph, [a, b], [edge_id], &disabled);
    assert!(plan.is_accept());
    assert_eq!(plan.ops().len(), 2);
    assert!(
        plan.ops()
            .iter()
            .any(|op| matches!(op, GraphOp::RemoveNode { id, .. } if *id == a))
    );
    assert!(
        plan.ops()
            .iter()
            .any(|op| matches!(op, GraphOp::RemoveNode { id, .. } if *id == b))
    );

    GraphTransaction::from_ops(plan.into_ops())
        .apply_to(&mut graph)
        .unwrap();

    assert!(!graph.nodes.contains_key(&a));
    assert!(!graph.nodes.contains_key(&b));
    assert!(!graph.edges.contains_key(&edge_id));
}