streamweave-attractor 0.3.0

Attractor pipeline as a StreamWeave graph
Documentation
//! Tests for the AttractorGraph → StreamWeave compiler.

use crate::compiler::compile_attractor_graph;
use crate::dot_parser::parse_dot;
use crate::runner::run_streamweave_graph;
use crate::types::GraphPayload;
use std::collections::HashMap;

#[test]
fn compile_rejects_exec_without_command() {
  let dot = r#"
    digraph G {
      graph [goal="test"]
      start [shape=Mdiamond]
      run [label="Run", type=exec]
      exit [shape=Msquare]
      start -> run -> exit
    }
  "#;
  let ast = parse_dot(dot).unwrap();
  match compile_attractor_graph(&ast, None, None, None) {
    Ok(_) => panic!("expected compile to fail (exec without command)"),
    Err(e) => {
      assert!(e.to_lowercase().contains("exec"));
      assert!(e.to_lowercase().contains("command"));
    }
  }
}

#[test]
fn compile_with_conditional_routing() {
  let dot = r#"
    digraph G {
      graph [goal="test"]
      start [shape=Mdiamond]
      run [type=exec, command="true"]
      fix [label="Fix"]
      exit [shape=Msquare]
      start -> run
      run -> exit [condition="outcome=success"]
      run -> fix [condition="outcome=fail"]
      fix -> run
    }
  "#;
  let ast = parse_dot(dot).unwrap();
  let graph = compile_attractor_graph(&ast, None, None, None).unwrap();
  // Graph built with direct out/error ports for run
  assert!(graph.name().contains("compiled"));
}

#[test]
fn compile_trivial_start_exit() {
  let dot = r#"
    digraph G {
      graph [goal="test"]
      start [shape=Mdiamond]
      exit [shape=Msquare]
      start -> exit
    }
  "#;
  let ast = parse_dot(dot).unwrap();
  let graph = compile_attractor_graph(&ast, None, None, None).unwrap();
  // Trivial case may be built via graph! or builder
  assert!(graph.find_node_by_name("start").is_some());
  assert!(graph.find_node_by_name("exit").is_some());
}

#[tokio::test]
async fn run_streamweave_graph_trivial_start_exit() {
  let dot = r#"
    digraph G {
      graph [goal="test"]
      start [shape=Mdiamond]
      exit [shape=Msquare]
      start -> exit
    }
  "#;
  let ast = parse_dot(dot).unwrap();
  let graph = compile_attractor_graph(&ast, None, None, None).unwrap();
  let initial = GraphPayload::initial(HashMap::new(), "start");
  let out = run_streamweave_graph(graph, initial).await.unwrap();
  // Identity path: one trigger in → one item out
  assert!(out.is_some(), "expected one output from start→exit graph");
}

#[test]
fn compile_err_no_start() {
  let dot = r#"
    digraph G {
      graph [goal="test"]
      exit [shape=Msquare]
    }
  "#;
  let ast = parse_dot(dot).unwrap();
  match compile_attractor_graph(&ast, None, None, None) {
    Ok(_) => panic!("expected compile to fail (no start)"),
    Err(e) => assert!(e.to_lowercase().contains("start")),
  }
}

#[test]
fn compile_err_no_exit() {
  let dot = r#"
    digraph G {
      graph [goal="test"]
      start [shape=Mdiamond]
    }
  "#;
  let ast = parse_dot(dot).unwrap();
  match compile_attractor_graph(&ast, None, None, None) {
    Ok(_) => panic!("expected compile to fail (no exit)"),
    Err(e) => assert!(e.to_lowercase().contains("exit")),
  }
}

#[test]
fn compile_pre_push_dot() {
  let path =
    std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/workflows/pre-push.dot");
  let dot = std::fs::read_to_string(&path).unwrap();
  let ast = parse_dot(&dot).unwrap();
  let graph = compile_attractor_graph(&ast, None, None, None).unwrap();
  assert!(graph.name().contains("compiled"));
}

#[test]
fn compile_rejects_invalid_entry_node_id() {
  let dot = r#"
    digraph G {
      graph [goal="test"]
      start [shape=Mdiamond]
      exit [shape=Msquare]
      start -> exit
    }
  "#;
  let ast = parse_dot(dot).unwrap();
  match compile_attractor_graph(&ast, Some("not_a_node"), None, None) {
    Ok(_) => panic!("expected compile to fail (invalid entry node id)"),
    Err(e) => {
      assert!(
        e.contains("not_a_node") && e.contains("not a node"),
        "error should mention invalid entry node: {}",
        e
      );
    }
  }
}

#[test]
fn compile_accepts_valid_entry_node_id_for_resume() {
  let dot = r#"
    digraph G {
      graph [goal="test"]
      start [shape=Mdiamond]
      exit [shape=Msquare]
      start -> exit
    }
  "#;
  let ast = parse_dot(dot).unwrap();
  let graph = compile_attractor_graph(&ast, Some("exit"), None, None).unwrap();
  assert!(graph.name().contains("compiled"));
  assert!(graph.find_node_by_name("start").is_some());
  assert!(graph.find_node_by_name("exit").is_some());
}