161 lines
5.8 KiB
Rust
161 lines
5.8 KiB
Rust
use codewhale_protocol::{
|
|
EventFrame, ThreadListParams, ThreadRequest, ThreadResumeParams,
|
|
runtime::{RUNTIME_EVENT_ENVELOPE_SCHEMA_VERSION, RuntimeEventEnvelope},
|
|
};
|
|
use serde_json::{Value, json};
|
|
|
|
#[test]
|
|
fn thread_resume_params_round_trip() {
|
|
let request = ThreadRequest::Resume(ThreadResumeParams {
|
|
thread_id: "thread-123".to_string(),
|
|
history: None,
|
|
path: None,
|
|
model: Some("deepseek-v4-pro".to_string()),
|
|
model_provider: Some("deepseek".to_string()),
|
|
cwd: None,
|
|
approval_policy: Some("on-request".to_string()),
|
|
sandbox: Some("workspace-write".to_string()),
|
|
config: None,
|
|
base_instructions: Some("base".to_string()),
|
|
developer_instructions: Some("dev".to_string()),
|
|
personality: Some("default".to_string()),
|
|
persist_extended_history: true,
|
|
});
|
|
|
|
let encoded = serde_json::to_string(&request).expect("serialize request");
|
|
let decoded: ThreadRequest = serde_json::from_str(&encoded).expect("deserialize request");
|
|
match decoded {
|
|
ThreadRequest::Resume(params) => {
|
|
assert_eq!(params.thread_id, "thread-123");
|
|
assert_eq!(params.model.as_deref(), Some("deepseek-v4-pro"));
|
|
assert!(params.persist_extended_history);
|
|
}
|
|
other => panic!("unexpected request: {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn thread_list_params_defaults_are_serializable() {
|
|
let request = ThreadRequest::List(ThreadListParams {
|
|
include_archived: false,
|
|
limit: Some(20),
|
|
});
|
|
let encoded = serde_json::to_string_pretty(&request).expect("serialize list request");
|
|
assert!(encoded.contains("include_archived"));
|
|
}
|
|
|
|
#[test]
|
|
fn event_frame_serialization_contains_expected_tag() {
|
|
let frame = EventFrame::TurnComplete {
|
|
turn_id: "turn-1".to_string(),
|
|
};
|
|
let encoded = serde_json::to_string(&frame).expect("serialize frame");
|
|
assert!(encoded.contains("turn_complete"));
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_event_envelope_roundtrip() {
|
|
let input = json!({
|
|
"schema_version": 1,
|
|
"seq": 12,
|
|
"event": "item.delta",
|
|
"kind": "item.delta",
|
|
"thread_id": "thr_123",
|
|
"turn_id": "turn_456",
|
|
"item_id": "item_789",
|
|
"timestamp": "2026-02-11T20:18:49.123Z",
|
|
"created_at": "2026-02-11T20:18:49.123Z",
|
|
"payload": { "delta": "ok", "kind": "agent_message" },
|
|
});
|
|
let envelope: RuntimeEventEnvelope =
|
|
serde_json::from_value(input).expect("deserialize runtime event envelope");
|
|
assert_eq!(envelope.schema_version, 1);
|
|
assert_eq!(envelope.seq, 12);
|
|
assert_eq!(envelope.event, "item.delta");
|
|
assert_eq!(envelope.kind, "item.delta");
|
|
assert_eq!(envelope.thread_id, "thr_123");
|
|
|
|
let encoded = serde_json::to_value(&envelope).expect("serialize runtime event envelope");
|
|
assert_eq!(encoded["event"], encoded["kind"]);
|
|
assert_eq!(encoded["schema_version"], 1);
|
|
assert_eq!(encoded["seq"], 12);
|
|
assert_eq!(encoded["thread_id"], "thr_123");
|
|
assert_eq!(encoded["turn_id"], "turn_456");
|
|
assert_eq!(encoded["item_id"], "item_789");
|
|
assert_eq!(encoded["timestamp"], "2026-02-11T20:18:49.123Z");
|
|
assert_eq!(encoded["created_at"], "2026-02-11T20:18:49.123Z");
|
|
assert_eq!(
|
|
encoded["payload"],
|
|
json!({ "delta": "ok", "kind": "agent_message" })
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_event_envelope_defaults_to_api_schema_version() {
|
|
let input = json!({
|
|
"seq": 15,
|
|
"event": "thread.started",
|
|
"kind": "thread.started",
|
|
"thread_id": "thr_default_version",
|
|
"timestamp": "2026-02-11T20:18:49.123Z",
|
|
"payload": {},
|
|
});
|
|
let envelope: RuntimeEventEnvelope = serde_json::from_value(input)
|
|
.expect("deserialize runtime event envelope without schema version");
|
|
|
|
assert_eq!(
|
|
envelope.schema_version,
|
|
RUNTIME_EVENT_ENVELOPE_SCHEMA_VERSION
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_event_envelope_thread_level_keeps_turn_and_item_ids() {
|
|
let input = json!({
|
|
"schema_version": 1,
|
|
"seq": 14,
|
|
"event": "thread.started",
|
|
"kind": "thread.started",
|
|
"thread_id": "thr_thread",
|
|
"timestamp": "2026-02-11T20:18:49.123Z",
|
|
"payload": { "thread": { "id": "thr_thread" } },
|
|
});
|
|
let envelope: RuntimeEventEnvelope = serde_json::from_value(input)
|
|
.expect("deserialize runtime event envelope without thread-level turn/item ids");
|
|
assert!(envelope.turn_id.is_none());
|
|
assert!(envelope.item_id.is_none());
|
|
|
|
let encoded = serde_json::to_value(envelope).expect("serialize runtime event envelope");
|
|
assert!(encoded.get("turn_id").is_some());
|
|
assert!(encoded.get("item_id").is_some());
|
|
assert!(encoded["turn_id"].is_null());
|
|
assert!(encoded["item_id"].is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_event_envelope_preserves_unknown_fields() {
|
|
let input: Value = json!({
|
|
"schema_version": 1,
|
|
"seq": 13,
|
|
"event": "turn.completed",
|
|
"kind": "turn.completed",
|
|
"thread_id": "thr_unknown",
|
|
"timestamp": "2026-02-11T20:18:49.123Z",
|
|
"payload": {},
|
|
"forward_compatibility_hint": "v2-ready",
|
|
});
|
|
let envelope: RuntimeEventEnvelope = serde_json::from_value(input.clone())
|
|
.expect("deserialize runtime event envelope with unknown field");
|
|
assert!(envelope.extra.contains_key("forward_compatibility_hint"));
|
|
|
|
let encoded = serde_json::to_value(envelope).expect("serialize runtime event envelope");
|
|
assert_eq!(encoded["forward_compatibility_hint"], "v2-ready");
|
|
assert_eq!(encoded["schema_version"], 1);
|
|
assert_eq!(encoded["seq"], 13);
|
|
assert_eq!(encoded["event"], "turn.completed");
|
|
assert_eq!(encoded["kind"], "turn.completed");
|
|
assert_eq!(encoded["thread_id"], "thr_unknown");
|
|
assert!(encoded["turn_id"].is_null());
|
|
assert!(encoded["item_id"].is_null());
|
|
}
|