jellyflow-core 0.2.0

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

#[test]
fn built_in_node_kind_constants_use_jellyflow_namespace() {
    assert_eq!(SUBGRAPH_NODE_KIND, "jellyflow.subgraph");
    assert_eq!(SYMBOL_REF_NODE_KIND, "jellyflow.symbol_ref");
}

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

    let node_id = NodeId::new();
    let mut node = make_node(SUBGRAPH_NODE_KIND);
    let imported = GraphId::from_u128(2);
    node.data = serde_json::json!({ "graph_id": imported });
    graph.nodes.insert(node_id, node);

    assert!(
        validate_subgraph_targets_are_imported(&graph).is_err(),
        "expected missing import to be rejected"
    );

    graph.imports.insert(imported, GraphImport::default());
    assert!(
        validate_subgraph_targets_are_imported(&graph).is_ok(),
        "expected declared import to satisfy binding"
    );
}

#[test]
fn subgraph_targets_must_resolve_through_import_closure() {
    let a = GraphId::from_u128(1);
    let b = GraphId::from_u128(2);
    let c = GraphId::from_u128(3);

    let mut g_a = Graph::new(a);
    g_a.imports.insert(b, GraphImport::default());

    let node_id = NodeId::new();
    let mut node = make_node(SUBGRAPH_NODE_KIND);
    node.data = serde_json::json!({ "graph_id": b });
    g_a.nodes.insert(node_id, node);

    let mut g_b = Graph::new(b);
    g_b.imports.insert(c, GraphImport::default());

    let g_c = Graph::new(c);

    let mut db = std::collections::BTreeMap::new();
    db.insert(a, g_a);
    db.insert(b, g_b);
    db.insert(c, g_c);

    let root = db.get(&a).expect("root graph must exist");

    validate_subgraph_targets_are_imported(root).expect("targets must be declared imports");
    let targets = collect_subgraph_targets(root).expect("collect targets");
    assert_eq!(targets.iter().copied().collect::<Vec<_>>(), vec![b]);

    let closure = resolve_import_closure(root, |id| db.get(&id)).expect("resolve closure");
    assert_eq!(closure.order, vec![c, b]);
    assert!(closure.contains(b));
    assert!(closure.contains(c));

    for target in targets {
        assert!(closure.contains(target));
    }
}

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

    let node_id = NodeId::new();
    let mut node = make_node(SUBGRAPH_NODE_KIND);
    let imported = GraphId::from_u128(2);
    node.data = serde_json::json!({ "graph_id": imported });
    graph.nodes.insert(node_id, node);

    let report = validate_graph_structural(&graph);
    assert!(report.errors.iter().any(|e| matches!(
        e,
        GraphValidationError::SubgraphTargetNotImported { node, graph_id }
            if *node == node_id && *graph_id == imported
    )));

    graph.imports.insert(imported, GraphImport::default());
    let report = validate_graph(&graph);
    assert!(report.is_ok());

    let mut bad = make_node(SUBGRAPH_NODE_KIND);
    bad.data = serde_json::json!({});
    let bad_id = NodeId::new();
    graph.nodes.insert(bad_id, bad);
    let report = validate_graph_structural(&graph);
    assert!(report.errors.iter().any(|e| matches!(
        e,
        GraphValidationError::SubgraphNodeMissingGraphId { node } if *node == bad_id
    )));
}