110 lines
4.4 KiB
Rust
110 lines
4.4 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 { .. })));
|
||
}
|
||
|
||
/// 工具调用 id 可显式指定(API 真实 id),空时退回自动生成。
|
||
/// Tool-call ids can be set explicitly (the API's real id); empty falls back
|
||
/// to auto-generation.
|
||
#[test]
|
||
fn reducer_tool_call_id_override() {
|
||
let mut r = ProviderEventReducer::new("m");
|
||
let _ = r.tool_call_start_with_id(0, "shell", "call_00_realId");
|
||
let _ = r.tool_call_start_with_id(1, "read", "");
|
||
let msg = r.finish().expect("final message");
|
||
let tc0 = msg.content[0].as_tool_call().expect("call 0");
|
||
let tc1 = msg.content[1].as_tool_call().expect("call 1");
|
||
assert_eq!(tc0.id, "call_00_realId");
|
||
assert_eq!(tc1.id, "call_1");
|
||
}
|