use super::{handshake::Version, stream};
use serde_json::{Value, json};
#[test]
fn r3_rows_25_30_small_tool_input_survives_large_input_drops_whole() -> anyhow::Result<()> {
let project = |input: Value| {
stream::project(
Version::One,
json!({"sessionId":"s","update":{
"sessionUpdate":"tool_call","toolCallId":"t","title":"Read","kind":"read","status":"completed","rawInput":input}}),
"s",
1024,
)
};
let small =
project(json!({"key":"small"}))?.ok_or_else(|| anyhow::anyhow!("small call absent"))?;
assert_eq!(small["input"]["key"], "small");
assert_eq!(small["truncated"], false);
let large = project(json!({"key":"x".repeat(65536)}))?
.ok_or_else(|| anyhow::anyhow!("large call absent"))?;
assert!(large.get("input").is_none());
assert_eq!(large["truncated"], true);
assert!(serde_json::to_vec(&large)?.len() <= 1024);
Ok(())
}
#[test]
fn r3_rows_25_26_28_29_projects_text_thought_plan_state() -> anyhow::Result<()> {
for (update, kind) in [
(
json!({"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"hello"}}),
"text",
),
(
json!({"sessionUpdate":"user_message_chunk","content":{"type":"text","text":"hello"}}),
"text",
),
(
json!({"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"reason"}}),
"thought",
),
(
json!({"sessionUpdate":"plan","entries":[{"content":"Read","status":"pending","priority":"high"}]}),
"plan",
),
] {
let item = stream::project(
Version::One,
json!({"sessionId":"s","update":update}),
"s",
1024,
)?
.ok_or_else(|| anyhow::anyhow!("item absent"))?;
assert_eq!(item["kind"], kind);
}
let item = stream::project(Version::Two,json!({"sessionId":"s","update":{"sessionUpdate":"state_update","state":"idle","stopReason":"end_turn"}}),"s",1024)?.ok_or_else(|| anyhow::anyhow!("state absent"))?;
assert_eq!(item["kind"], "state");
assert_eq!(item["stop_reason"], "end_turn");
Ok(())
}
#[test]
fn r3_row_30_unicode_fits_including_envelope() -> anyhow::Result<()> {
let mut item =
json!({"kind":"text","text":"é🦀".repeat(1000),"conversation":"s","seq":1,"durable":false});
stream::fit(&mut item, 256)?;
assert!(serde_json::to_vec(&item)?.len() <= 256);
assert_eq!(item["truncated"], true);
assert!(
item["text"]
.as_str()
.is_some_and(|s| s.is_char_boundary(s.len()))
);
Ok(())
}
#[test]
fn r3_rows_25_28_v2_whole_messages_and_plan_updates() -> anyhow::Result<()> {
for (update, kind) in [
(
json!({"sessionUpdate":"agent_message","messageId":"m","content":[{"type":"text","text":"Whole message"}]}),
"text",
),
(
json!({"sessionUpdate":"plan_update","plan":{"type":"items","planId":"p","entries":[{"content":"Read","status":"pending","priority":"high"}]}}),
"plan",
),
] {
let item = stream::project(
Version::Two,
json!({"sessionId":"s","update":update}),
"s",
1024,
)?
.ok_or_else(|| anyhow::anyhow!("whole update absent"))?;
assert_eq!(item["kind"], kind);
if kind == "text" {
assert_eq!(item["text"], "Whole message");
assert_eq!(item["message_id"], "m");
} else {
assert_eq!(item["entries"][0]["content"], "Read");
}
}
Ok(())
}
#[test]
fn r3_row_25_metadata_message_does_not_clear_existing_content() -> anyhow::Result<()> {
let project = |update| {
stream::project(
Version::Two,
json!({"sessionId":"s","update":update}),
"s",
1024,
)
};
let omitted = project(json!({"sessionUpdate":"agent_message","messageId":"m"}))?
.ok_or_else(|| anyhow::anyhow!("metadata message absent"))?;
assert!(
omitted.get("text").is_none(),
"omitted content became a replacement"
);
let cleared = project(json!({"sessionUpdate":"agent_message","messageId":"m","content":null}))?
.ok_or_else(|| anyhow::anyhow!("clear message absent"))?;
assert_eq!(cleared["text"], "");
assert_eq!(cleared["replace"], true);
Ok(())
}
#[test]
fn r1_stream_decode_failure_does_not_echo_harness_values() -> anyhow::Result<()> {
for version in [Version::One, Version::Two] {
let error = super::stream::project(
version,
json!({"sessionId":"session","update":"fixture-private-value"}),
"session",
65536,
)
.err()
.ok_or_else(|| anyhow::anyhow!("malformed update accepted"))?;
assert!(error.to_string().contains("acp_session_update_invalid"));
assert!(!error.to_string().contains("fixture-private-value"));
}
Ok(())
}