Files
zclaw_openfang/crates/zclaw-kernel/tests/smoke_hands.rs
iven 591af5802c
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
test(kernel,growth): Phase 1 缝测试安全网 — 3条核心链路 19 测试全部通过
对话链路: 4 缝测试 (Tauri→Kernel / Kernel→LLM / LLM→UI / 流式生命周期)
Hands链路: 3 缝测试 (工具路由 / 执行回调 / 通用工具)
记忆链路: 3 缝测试 (FTS5存储 / 模式检索 / 去重)
冒烟测试: 3 Rust + 8 TypeScript 全量 PASS
- Kernel::boot_with_driver() 测试辅助方法
- 全量 cargo test 0 回归

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 21:36:46 +08:00

94 lines
3.6 KiB
Rust

//! Hands smoke test — full lifecycle: trigger tool_call → hand execute → result
//!
//! Uses MockLlmDriver with stream chunks to simulate a real tool call flow.
use std::sync::Arc;
use zclaw_kernel::{Kernel, KernelConfig};
use zclaw_runtime::stream::StreamChunk;
use zclaw_runtime::test_util::MockLlmDriver;
use zclaw_runtime::{LoopEvent, LlmDriver};
use zclaw_types::AgentConfig;
#[tokio::test]
async fn smoke_hands_full_lifecycle() {
// Simulate: LLM calls hand_quiz → quiz hand executes → LLM summarizes
let mock = MockLlmDriver::new()
.with_stream_chunks(vec![
StreamChunk::TextDelta { delta: "正在生成测验...".to_string() },
StreamChunk::ToolUseStart {
id: "call_1".to_string(),
name: "hand_quiz".to_string(),
},
StreamChunk::ToolUseEnd {
id: "call_1".to_string(),
input: serde_json::json!({ "topic": "历史", "count": 2 }),
},
StreamChunk::Complete {
input_tokens: 15,
output_tokens: 10,
stop_reason: "tool_use".to_string(),
},
])
// After hand_quiz returns, LLM generates final response
.with_stream_chunks(vec![
StreamChunk::TextDelta { delta: "测验已生成!".to_string() },
StreamChunk::Complete {
input_tokens: 20,
output_tokens: 5,
stop_reason: "end_turn".to_string(),
},
]);
let config = KernelConfig::default();
let kernel = Kernel::boot_with_driver(config, Arc::new(mock) as Arc<dyn LlmDriver>)
.await
.expect("kernel boot");
let agent = AgentConfig::new("smoke-agent");
let id = agent.id;
kernel.spawn_agent(agent).await.expect("spawn agent");
let mut rx = kernel
.send_message_stream(&id, "生成一个历史测验".to_string())
.await
.expect("stream");
let mut saw_tool_start = false;
let mut saw_tool_end = false;
let mut saw_delta_before_tool = false;
let mut saw_delta_after_tool = false;
let mut phase = "before_tool";
let mut got_complete = false;
while let Some(event) = rx.recv().await {
match event {
LoopEvent::Delta(_) if phase == "before_tool" => saw_delta_before_tool = true,
LoopEvent::Delta(_) if phase == "after_tool" => saw_delta_after_tool = true,
LoopEvent::ToolStart { name, .. } => {
assert_eq!(name, "hand_quiz", "should be hand_quiz");
saw_tool_start = true;
}
LoopEvent::ToolEnd { name, output } => {
assert!(name.starts_with("hand_"), "should be hand tool");
assert!(output.is_object() || output.is_string(), "hand should produce output");
saw_tool_end = true;
phase = "after_tool";
}
LoopEvent::Complete(result) => {
assert!(result.output_tokens > 0, "should have output tokens");
assert!(result.iterations >= 2, "should take at least 2 iterations");
got_complete = true;
break;
}
LoopEvent::Error(msg) => panic!("unexpected error: {}", msg),
_ => {}
}
}
assert!(saw_delta_before_tool, "should see delta before tool execution");
assert!(saw_tool_start, "should see hand_quiz ToolStart");
assert!(saw_tool_end, "should see hand_quiz ToolEnd");
assert!(saw_delta_after_tool, "should see delta after tool execution");
assert!(got_complete, "should receive complete event");
}