95 lines
3.7 KiB
Rust
95 lines
3.7 KiB
Rust
//! provider 模块(流事件类型)的集成测试。
|
|
//! Integration tests for the provider module (stream event types).
|
|
|
|
use focus_core::model::{AssistantMessage, StopReason, Usage};
|
|
use focus_core::provider::{ProviderEventReducer, StreamEvent};
|
|
|
|
#[test]
|
|
fn stream_event_display() {
|
|
assert_eq!(
|
|
StreamEvent::Start {
|
|
partial: AssistantMessage {
|
|
content: vec![],
|
|
model: "m".into(),
|
|
usage: Usage::default(),
|
|
stop_reason: StopReason::Stop,
|
|
error_message: None,
|
|
timestamp: 0,
|
|
}
|
|
}
|
|
.to_string(),
|
|
"start"
|
|
);
|
|
}
|
|
|
|
/// 文本与工具调用交错时,参数必须归到正确的调用槽(回归测试)。
|
|
/// When text and tool calls interleave, arguments must land in the correct
|
|
/// call slot (regression test).
|
|
#[test]
|
|
fn reducer_interleaved_text_and_tool_calls() {
|
|
let mut r = ProviderEventReducer::new("m");
|
|
let mut events = r.text_delta("Let me check. ");
|
|
events.extend(r.tool_call_start(0, "read"));
|
|
events.extend(r.tool_call_delta(0, r#"{"path":"a""#));
|
|
// 第二个工具调用与第一个交错。
|
|
// Second tool call interleaved with the first.
|
|
events.extend(r.tool_call_start(1, "write"));
|
|
events.extend(r.tool_call_delta(1, r#"{"path":"b""#));
|
|
events.extend(r.tool_call_delta(0, r#"}"#));
|
|
events.extend(r.tool_call_delta(1, r#"}"#));
|
|
events.extend(r.text_delta(" Done."));
|
|
r.set_stop(StopReason::ToolUse);
|
|
events.extend(r.finalize_events());
|
|
let msg = r.finish().expect("final message");
|
|
|
|
// 内容顺序:文本块 + 两个工具调用(后续文本追加到同一文本块)。
|
|
// Content order: one text block + two tool calls (later text appends to
|
|
// the same text block).
|
|
assert_eq!(msg.content.len(), 3);
|
|
let head = msg.content[0].as_text().expect("leading text");
|
|
assert_eq!(head.text, "Let me check. Done.");
|
|
let tc0 = msg.content[1].as_tool_call().expect("tool call 0");
|
|
let tc1 = msg.content[2].as_tool_call().expect("tool call 1");
|
|
assert_eq!(tc0.name, "read");
|
|
assert_eq!(tc0.arguments.get_str("path"), Some("a"));
|
|
assert_eq!(tc1.name, "write");
|
|
assert_eq!(tc1.arguments.get_str("path"), Some("b"));
|
|
assert_eq!(msg.stop_reason, StopReason::ToolUse);
|
|
// 事件流必须包含每个工具调用的 end 事件。
|
|
// The event stream must include an end event per tool call.
|
|
let end_events = events
|
|
.iter()
|
|
.filter(|e| matches!(e, StreamEvent::ToolCallEnd { .. }))
|
|
.count();
|
|
assert_eq!(end_events, 2);
|
|
}
|
|
|
|
/// 思考块出现在文本之前时,两者都必须正确累积。
|
|
/// When a thinking block precedes text, both must accumulate correctly.
|
|
#[test]
|
|
fn reducer_thinking_then_text() {
|
|
let mut r = ProviderEventReducer::new("m");
|
|
let mut events = r.thinking_delta("Let me reason");
|
|
events.extend(r.thinking_delta(" step by step."));
|
|
events.extend(r.text_delta("Answer: 42"));
|
|
events.extend(r.finalize_events());
|
|
let msg = r.finish().expect("final message");
|
|
|
|
assert_eq!(msg.content.len(), 2);
|
|
let thinking = match &msg.content[0] {
|
|
focus_core::model::ContentBlock::Thinking(t) => t.thinking.clone(),
|
|
other => panic!("expected thinking block, got {:?}", other),
|
|
};
|
|
assert_eq!(thinking, "Let me reason step by step.");
|
|
let text = msg.content[1].as_text().expect("text block");
|
|
assert_eq!(text.text, "Answer: 42");
|
|
// 思考必须有自己的 start/delta/end 事件。
|
|
// Thinking must have its own start/delta/end events.
|
|
assert!(events
|
|
.iter()
|
|
.any(|e| matches!(e, StreamEvent::ThinkingStart { .. })));
|
|
assert!(events
|
|
.iter()
|
|
.any(|e| matches!(e, StreamEvent::ThinkingEnd { .. })));
|
|
}
|