//! Conversation chain seam tests //! //! Verifies the integration seams between layers in the chat pipeline: //! 1. Tauri→Kernel: chat command correctly forwards to kernel //! 2. Kernel→LLM: middleware-processed prompt reaches MockLlmDriver //! 3. LLM→UI: event ordering is delta → delta → complete //! 4. Streaming: full send→stream→complete lifecycle use std::sync::Arc; use zclaw_kernel::{Kernel, KernelConfig}; use zclaw_runtime::test_util::MockLlmDriver; use zclaw_runtime::{LoopEvent, LlmDriver}; use zclaw_types::AgentConfig; /// Create a test kernel with MockLlmDriver and a registered agent. /// The mock is pre-configured with a default text response. async fn test_kernel() -> (Kernel, zclaw_types::AgentId) { let mock = MockLlmDriver::new().with_text_response("Hello from mock!"); let config = KernelConfig::default(); let kernel = Kernel::boot_with_driver(config, Arc::new(mock) as Arc) .await .expect("kernel boot"); let agent_config = AgentConfig::new("test-agent") .with_system_prompt("You are a test assistant."); let id = agent_config.id; kernel.spawn_agent(agent_config).await.expect("spawn agent"); (kernel, id) } // --------------------------------------------------------------------------- // Seam 1: Tauri → Kernel (non-streaming) // --------------------------------------------------------------------------- #[tokio::test] async fn seam_tauri_to_kernel_non_streaming() { let (kernel, agent_id) = test_kernel().await; let result = kernel .send_message(&agent_id, "Hi".to_string()) .await .expect("send_message"); assert!(!result.content.is_empty(), "response content should not be empty"); } // --------------------------------------------------------------------------- // Seam 2: Kernel → LLM (middleware processes prompt before reaching driver) // --------------------------------------------------------------------------- #[tokio::test] async fn seam_kernel_to_llm_prompt_reaches_driver() { let (kernel, agent_id) = test_kernel().await; let _ = kernel .send_message(&agent_id, "What is 2+2?".to_string()) .await; // Verify the kernel's driver was called by checking a second call succeeds let result2 = kernel .send_message(&agent_id, "And 3+3?".to_string()) .await .expect("second send_message"); assert!(!result2.content.is_empty(), "second response should not be empty"); } // --------------------------------------------------------------------------- // Seam 3: LLM → UI event ordering (delta → delta → complete) // --------------------------------------------------------------------------- #[tokio::test] async fn seam_llm_to_ui_event_ordering() { let (kernel, agent_id) = test_kernel().await; let mut rx = kernel .send_message_stream(&agent_id, "Hi".to_string()) .await .expect("send_message_stream"); let mut events = Vec::new(); while let Some(event) = rx.recv().await { match &event { LoopEvent::Delta(_) => events.push("delta"), LoopEvent::ThinkingDelta(_) => events.push("thinking"), LoopEvent::Complete(_) => { events.push("complete"); break; } LoopEvent::Error(msg) => { panic!("unexpected error: {}", msg); } LoopEvent::ToolStart { .. } => events.push("tool_start"), LoopEvent::ToolEnd { .. } => events.push("tool_end"), LoopEvent::SubtaskStatus { .. } => events.push("subtask"), LoopEvent::IterationStart { .. } => events.push("iteration"), } } assert!(!events.is_empty(), "should receive events"); assert_eq!(events.last(), Some(&"complete"), "last event must be complete"); assert!( events.iter().any(|e| *e == "delta"), "should have at least one delta event" ); } // --------------------------------------------------------------------------- // Seam 4: Full streaming lifecycle with consecutive messages // --------------------------------------------------------------------------- #[tokio::test] async fn seam_streaming_consecutive_messages() { let (kernel, agent_id) = test_kernel().await; // First message let mut rx1 = kernel .send_message_stream(&agent_id, "First message".to_string()) .await .expect("first stream"); while let Some(event) = rx1.recv().await { if let LoopEvent::Complete(result) = event { assert!(result.output_tokens > 0, "first response should have output tokens"); } } // Second message (should use new session) let mut rx2 = kernel .send_message_stream(&agent_id, "Second message".to_string()) .await .expect("second stream"); let mut got_complete = false; while let Some(event) = rx2.recv().await { if let LoopEvent::Complete(result) = event { got_complete = true; assert!(result.output_tokens > 0, "second response should have output tokens"); } } assert!(got_complete, "second stream should complete"); }