//! End-to-end workflow integration tests. //! //! Tests the full pipeline: boot kernel → spawn agents → create workflow → //! execute workflow → verify outputs flow through the pipeline. //! //! LLM tests require GROQ_API_KEY. Non-LLM tests verify the kernel-level //! workflow wiring without making real API calls. use openfang_kernel::workflow::{ ErrorMode, StepAgent, StepMode, Workflow, WorkflowId, WorkflowStep, }; use openfang_kernel::OpenFangKernel; use openfang_types::agent::AgentManifest; use openfang_types::config::{DefaultModelConfig, KernelConfig}; use std::sync::Arc; fn test_config(provider: &str, model: &str, api_key_env: &str) -> KernelConfig { let tmp = tempfile::tempdir().unwrap(); KernelConfig { home_dir: tmp.path().to_path_buf(), data_dir: tmp.path().join("data"), default_model: DefaultModelConfig { provider: provider.to_string(), model: model.to_string(), api_key_env: api_key_env.to_string(), base_url: None, }, ..KernelConfig::default() } } fn spawn_test_agent( kernel: &OpenFangKernel, name: &str, system_prompt: &str, ) -> openfang_types::agent::AgentId { let manifest_str = format!( r#" name = "{name}" version = "0.1.0" description = "Workflow test agent: {name}" author = "test" module = "builtin:chat" [model] provider = "groq" model = "llama-3.3-70b-versatile" system_prompt = "{system_prompt}" [capabilities] memory_read = ["*"] memory_write = ["self.*"] "# ); let manifest: AgentManifest = toml::from_str(&manifest_str).unwrap(); kernel.spawn_agent(manifest).expect("Agent should spawn") } // --------------------------------------------------------------------------- // Kernel-level workflow wiring tests (no LLM needed) // --------------------------------------------------------------------------- /// Test that workflow registration and agent resolution work at the kernel level. #[tokio::test] async fn test_workflow_register_and_resolve() { let config = test_config("ollama", "test-model", "OLLAMA_API_KEY"); let kernel = OpenFangKernel::boot_with_config(config).expect("Kernel should boot"); let kernel = Arc::new(kernel); // Spawn agents let manifest: AgentManifest = toml::from_str( r#" name = "agent-alpha" version = "0.1.0" description = "Alpha" author = "test" module = "builtin:chat" [model] provider = "ollama" model = "test" system_prompt = "Alpha." [capabilities] memory_read = ["*"] memory_write = ["self.*"] "#, ) .unwrap(); let alpha_id = kernel.spawn_agent(manifest).unwrap(); let manifest2: AgentManifest = toml::from_str( r#" name = "agent-beta" version = "0.1.0" description = "Beta" author = "test" module = "builtin:chat" [model] provider = "ollama" model = "test" system_prompt = "Beta." [capabilities] memory_read = ["*"] memory_write = ["self.*"] "#, ) .unwrap(); let beta_id = kernel.spawn_agent(manifest2).unwrap(); // Create a 2-step workflow referencing agents by name let workflow = Workflow { id: WorkflowId::new(), name: "alpha-beta-pipeline".to_string(), description: "Tests agent resolution by name".to_string(), steps: vec![ WorkflowStep { name: "step-alpha".to_string(), agent: StepAgent::ByName { name: "agent-alpha".to_string(), }, prompt_template: "Analyze: {{input}}".to_string(), mode: StepMode::Sequential, timeout_secs: 30, error_mode: ErrorMode::Fail, output_var: Some("alpha_out".to_string()), }, WorkflowStep { name: "step-beta".to_string(), agent: StepAgent::ByName { name: "agent-beta".to_string(), }, prompt_template: "Summarize: {{input}} (alpha said: {{alpha_out}})".to_string(), mode: StepMode::Sequential, timeout_secs: 30, error_mode: ErrorMode::Fail, output_var: None, }, ], created_at: chrono::Utc::now(), }; let wf_id = kernel.register_workflow(workflow).await; // Verify workflow is registered let workflows = kernel.workflows.list_workflows().await; assert_eq!(workflows.len(), 1); assert_eq!(workflows[0].name, "alpha-beta-pipeline"); // Verify agents can be found by name let alpha = kernel.registry.find_by_name("agent-alpha"); assert!(alpha.is_some()); assert_eq!(alpha.unwrap().id, alpha_id); let beta = kernel.registry.find_by_name("agent-beta"); assert!(beta.is_some()); assert_eq!(beta.unwrap().id, beta_id); // Verify workflow run can be created let run_id = kernel .workflows .create_run(wf_id, "test input".to_string()) .await; assert!(run_id.is_some()); let run = kernel.workflows.get_run(run_id.unwrap()).await.unwrap(); assert_eq!(run.input, "test input"); kernel.shutdown(); } /// Test workflow with agent referenced by ID. #[tokio::test] async fn test_workflow_agent_by_id() { let config = test_config("ollama", "test-model", "OLLAMA_API_KEY"); let kernel = OpenFangKernel::boot_with_config(config).expect("Kernel should boot"); let manifest: AgentManifest = toml::from_str( r#" name = "id-agent" version = "0.1.0" description = "Test" author = "test" module = "builtin:chat" [model] provider = "ollama" model = "test" system_prompt = "Test." [capabilities] memory_read = ["*"] memory_write = ["self.*"] "#, ) .unwrap(); let agent_id = kernel.spawn_agent(manifest).unwrap(); let workflow = Workflow { id: WorkflowId::new(), name: "by-id-test".to_string(), description: "".to_string(), steps: vec![WorkflowStep { name: "step1".to_string(), agent: StepAgent::ById { id: agent_id.to_string(), }, prompt_template: "{{input}}".to_string(), mode: StepMode::Sequential, timeout_secs: 30, error_mode: ErrorMode::Fail, output_var: None, }], created_at: chrono::Utc::now(), }; let wf_id = kernel.register_workflow(workflow).await; // Can create run (agent resolution happens at execute time) let run_id = kernel .workflows .create_run(wf_id, "hello".to_string()) .await; assert!(run_id.is_some()); kernel.shutdown(); } /// Test trigger registration and listing at kernel level. #[tokio::test] async fn test_trigger_registration_with_kernel() { use openfang_kernel::triggers::TriggerPattern; let config = test_config("ollama", "test-model", "OLLAMA_API_KEY"); let kernel = OpenFangKernel::boot_with_config(config).expect("Kernel should boot"); let manifest: AgentManifest = toml::from_str( r#" name = "trigger-agent" version = "0.1.0" description = "Trigger test" author = "test" module = "builtin:chat" [model] provider = "ollama" model = "test" system_prompt = "Test." [capabilities] memory_read = ["*"] memory_write = ["self.*"] "#, ) .unwrap(); let agent_id = kernel.spawn_agent(manifest).unwrap(); // Register triggers let t1 = kernel .register_trigger( agent_id, TriggerPattern::Lifecycle, "Lifecycle event: {{event}}".to_string(), 0, ) .unwrap(); let t2 = kernel .register_trigger( agent_id, TriggerPattern::SystemKeyword { keyword: "deploy".to_string(), }, "Deploy event: {{event}}".to_string(), 5, ) .unwrap(); // List all triggers let all = kernel.list_triggers(None); assert_eq!(all.len(), 2); // List triggers for specific agent let agent_triggers = kernel.list_triggers(Some(agent_id)); assert_eq!(agent_triggers.len(), 2); // Remove one assert!(kernel.remove_trigger(t1)); let remaining = kernel.list_triggers(None); assert_eq!(remaining.len(), 1); assert_eq!(remaining[0].id, t2); kernel.shutdown(); } // --------------------------------------------------------------------------- // Full E2E with real LLM (skip if no GROQ_API_KEY) // --------------------------------------------------------------------------- /// End-to-end: boot kernel → spawn 2 agents → create 2-step workflow → /// run it through the real Groq LLM → verify output flows from step 1 to step 2. #[tokio::test] async fn test_workflow_e2e_with_groq() { if std::env::var("GROQ_API_KEY").is_err() { eprintln!("GROQ_API_KEY not set, skipping E2E workflow test"); return; } let config = test_config("groq", "llama-3.3-70b-versatile", "GROQ_API_KEY"); let kernel = OpenFangKernel::boot_with_config(config).expect("Kernel should boot"); let kernel = Arc::new(kernel); kernel.set_self_handle(); // Spawn two agents with distinct roles let _analyst_id = spawn_test_agent( &kernel, "wf-analyst", "You are an analyst. When given text, respond with exactly: ANALYSIS: followed by a one-sentence analysis.", ); let _writer_id = spawn_test_agent( &kernel, "wf-writer", "You are a writer. When given text, respond with exactly: SUMMARY: followed by a one-sentence summary.", ); // Create a 2-step pipeline: analyst → writer let workflow = Workflow { id: WorkflowId::new(), name: "analyst-writer-pipeline".to_string(), description: "E2E integration test workflow".to_string(), steps: vec![ WorkflowStep { name: "analyze".to_string(), agent: StepAgent::ByName { name: "wf-analyst".to_string(), }, prompt_template: "Analyze the following: {{input}}".to_string(), mode: StepMode::Sequential, timeout_secs: 60, error_mode: ErrorMode::Fail, output_var: None, }, WorkflowStep { name: "summarize".to_string(), agent: StepAgent::ByName { name: "wf-writer".to_string(), }, prompt_template: "Summarize this analysis: {{input}}".to_string(), mode: StepMode::Sequential, timeout_secs: 60, error_mode: ErrorMode::Fail, output_var: None, }, ], created_at: chrono::Utc::now(), }; let wf_id = kernel.register_workflow(workflow).await; // Run the workflow let result = kernel .run_workflow( wf_id, "The Rust programming language is growing rapidly.".to_string(), ) .await; assert!( result.is_ok(), "Workflow should complete: {:?}", result.err() ); let (run_id, output) = result.unwrap(); println!("\n=== WORKFLOW OUTPUT ==="); println!("{output}"); println!("======================\n"); assert!(!output.is_empty(), "Workflow output should not be empty"); // Verify the workflow run record let run = kernel.workflows.get_run(run_id).await.unwrap(); assert!(matches!( run.state, openfang_kernel::workflow::WorkflowRunState::Completed )); assert_eq!(run.step_results.len(), 2); assert_eq!(run.step_results[0].step_name, "analyze"); assert_eq!(run.step_results[1].step_name, "summarize"); // Both steps should have used tokens assert!(run.step_results[0].input_tokens > 0); assert!(run.step_results[0].output_tokens > 0); assert!(run.step_results[1].input_tokens > 0); assert!(run.step_results[1].output_tokens > 0); // List runs let runs = kernel.workflows.list_runs(None).await; assert_eq!(runs.len(), 1); kernel.shutdown(); }