weavatrix-rust 2.16.2

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, n8n, Dify, Agent catalogs, Mermaid, and Web3 ABI
Documentation
mod n8n_support;

use blazingly_json::json;
use n8n_support::{dump, engine, labels};
use weavatrix_rust::tools;

#[test]
fn ordinary_json_manifests_do_not_become_n8n() {
    let (_fixture, engine) = engine();
    assert!(
        engine
            .state()
            .graph()
            .nodes()
            .iter()
            .filter(|node| node.label == "n8n-fixtures")
            .all(|node| node.kind.as_str() != "n8n.workflow")
    );
}

#[test]
fn inventory_trace_and_context_work_on_one_export_catalog() {
    let (_fixture, mut engine) = engine();
    let inventory = tools::call(&mut engine, "n8n_inventory", json!({})).unwrap();
    let workflows = inventory["workflows"].as_array().unwrap();
    assert!(
        workflows
            .iter()
            .any(|item| item["label"] == "Invoice Reminder"),
        "{}",
        dump(&inventory)
    );
    assert!(
        workflows
            .iter()
            .any(|item| item["label"] == "Load Customer Workflow"),
        "provided subworkflow must stay in the catalog: {}",
        dump(&inventory)
    );
    let send = labels(&engine, "Send Invoice");
    assert_eq!(send.len(), 1, "{send:?}");
    let trace = tools::call(
        &mut engine,
        "n8n_trace",
        json!({"label": "Send Invoice", "depth": 6, "max_nodes": 100}),
    )
    .unwrap();
    let steps = dump(&trace);
    assert!(
        steps.contains("flows_to") || steps.contains("depends_on_output"),
        "{steps}"
    );
    assert!(!steps.to_ascii_lowercase().contains("infinite"));
    let context = tools::call(
        &mut engine,
        "n8n_context",
        json!({"label": "Send Invoice", "task": "change email recipient"}),
    )
    .unwrap();
    let text = dump(&context);
    assert!(
        text.contains("email") || text.contains("Load Customer"),
        "{text}"
    );
    assert!(
        text.contains("runtime response shape is not provided"),
        "{text}"
    );
    assert_eq!(context["coverage"]["runtime"]["provided"], false);
}

#[test]
fn diamond_merge_is_not_a_cycle_and_keeps_both_in_edges() {
    let (_fixture, mut engine) = engine();
    let trace = tools::call(
        &mut engine,
        "n8n_trace",
        json!({"label": "Merge", "depth": 8, "max_nodes": 100, "direction": "incoming"}),
    )
    .unwrap();
    assert_eq!(trace["cycle"], false, "{}", dump(&trace));
    let steps = trace["steps"].as_array().unwrap();
    let flows = steps
        .iter()
        .filter(|step| step["relation"] == "flows_to")
        .collect::<Vec<_>>();
    let details = flows
        .iter()
        .filter_map(|step| step["detail"].as_str())
        .collect::<Vec<_>>();
    assert!(
        details
            .iter()
            .any(|detail| detail.contains("Branch A") && detail.contains("Merge")),
        "{details:?}"
    );
    assert!(
        details
            .iter()
            .any(|detail| detail.contains("Branch B") && detail.contains("Merge")),
        "{details:?}"
    );
}

#[test]
fn inventory_max_results_reports_the_cut() {
    let (_fixture, mut engine) = engine();
    let inventory = tools::call(&mut engine, "n8n_inventory", json!({"max_results": 1})).unwrap();
    assert_eq!(inventory["workflows"].as_array().unwrap().len(), 1);
    assert_eq!(inventory["bounds"]["truncated"], true);
    assert!(inventory["bounds"]["found"].as_u64().unwrap() > 1);
}

#[test]
fn if_and_merge_keep_every_port_index() {
    let (_fixture, engine) = engine();
    let labels = engine
        .state()
        .graph()
        .nodes()
        .iter()
        .map(|node| node.label.as_str())
        .collect::<Vec<_>>();
    let joined = labels.join(" | ");
    assert!(
        joined.contains("Check Customer/main:0 -> Branch A/main:0"),
        "{joined}"
    );
    assert!(
        joined.contains("Check Customer/main:1 -> Branch B/main:0"),
        "{joined}"
    );
    assert!(
        joined.contains("Branch B/main:0 -> Merge/main:1"),
        "merge input 1 must survive: {joined}"
    );
}

#[test]
fn same_display_names_in_different_workflows_do_not_merge() {
    let (_fixture, mut engine) = engine();
    let ids = labels(&engine, "Load Customer");
    assert_eq!(ids.len(), 2, "{ids:?}");
    let error =
        tools::call(&mut engine, "n8n_trace", json!({"label": "Load Customer"})).unwrap_err();
    assert!(error.contains("ambiguous"), "{error}");
}

#[test]
fn n8n_profile_exposes_the_three_domain_tools() {
    let catalog = tools::catalog_for_profile(tools::ToolProfile::N8n);
    let names = catalog.iter().map(|tool| tool.name).collect::<Vec<_>>();
    assert!(names.contains(&"n8n_inventory"));
    assert!(names.contains(&"n8n_trace"));
    assert!(names.contains(&"n8n_context"));
    assert!(!names.contains(&"verified_change"));
}