use super::*;
use brazen::{Content, Role};
use serde_json::json;
use tempfile::TempDir;
fn write(worktree: &Path, name: &str, bytes: &[u8]) {
let dir = worktree.join(MESSAGES_DIR);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join(name), bytes).unwrap();
}
fn write_json(worktree: &Path, name: &str, blocks: &[Content]) {
write(worktree, name, &serde_json::to_vec(blocks).unwrap());
}
#[test]
fn absent_messages_dir_assembles_no_messages() {
let dir = TempDir::new().unwrap();
assert!(assemble(dir.path(), None).unwrap().is_empty());
}
#[test]
fn empty_messages_dir_assembles_no_messages() {
let dir = TempDir::new().unwrap();
std::fs::create_dir_all(dir.path().join(MESSAGES_DIR)).unwrap();
assert!(assemble(dir.path(), None).unwrap().is_empty());
}
#[test]
fn messages_path_that_is_a_file_surfaces_io_error() {
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join(MESSAGES_DIR), b"not a dir").unwrap();
assert!(matches!(
assemble(dir.path(), None).unwrap_err(),
Error::Io(_)
));
}
#[test]
fn user_message_composes_as_a_user_text_block_verbatim() {
let dir = TempDir::new().unwrap();
write(dir.path(), "001-user.md", b"list files");
let msgs = assemble(dir.path(), None).unwrap();
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0].role, Role::User);
assert_eq!(msgs[0].content, vec![Content::Text("list files".into())]);
}
#[test]
fn replay_from_a_recorded_tree_yields_the_alternating_wire_history() {
let dir = TempDir::new().unwrap();
write(dir.path(), "001-user.md", b"go");
write_json(
dir.path(),
"002-claude-fable-5.json",
&[Content::ToolUse {
id: "toolu_1".into(),
name: "bash".into(),
input: json!({"cmd": "ls"}),
signature: None,
}],
);
write_json(
dir.path(),
"003-tool.json",
&[Content::ToolResult {
tool_use_id: "toolu_1".into(),
content: vec![Content::Text("out".into())],
is_error: false,
}],
);
let msgs = assemble(dir.path(), None).unwrap();
assert_eq!(msgs.len(), 3);
assert_eq!(msgs[0].role, Role::User);
assert_eq!(msgs[1].role, Role::Assistant);
assert!(matches!(msgs[1].content[0], Content::ToolUse { .. }));
assert_eq!(msgs[2].role, Role::Tool);
assert!(matches!(msgs[2].content[0], Content::ToolResult { .. }));
}
#[test]
fn consecutive_same_side_entries_group_into_one_message() {
let dir = TempDir::new().unwrap();
write(dir.path(), "001-user.md", b"do two");
write_json(
dir.path(),
"002-claude-fable-5.json",
&[
Content::ToolUse {
id: "a".into(),
name: "bash".into(),
input: json!({}),
signature: None,
},
Content::ToolUse {
id: "b".into(),
name: "read_file".into(),
input: json!({}),
signature: None,
},
],
);
write_json(dir.path(), "003-tool.json", &[tool_result("a")]);
write_json(dir.path(), "004-tool.json", &[tool_result("b")]);
let msgs = assemble(dir.path(), None).unwrap();
assert_eq!(msgs.len(), 3);
assert_eq!(msgs[2].role, Role::Tool);
assert_eq!(msgs[2].content.len(), 2);
assert!(
matches!(&msgs[2].content[0], Content::ToolResult { tool_use_id, .. } if tool_use_id == "a")
);
assert!(
matches!(&msgs[2].content[1], Content::ToolResult { tool_use_id, .. } if tool_use_id == "b")
);
}
#[test]
fn entries_sort_by_numeric_prefix_and_ignore_non_conforming_names() {
let dir = TempDir::new().unwrap();
write_json(
dir.path(),
"002-claude-fable-5.json",
&[Content::Text("second".into())],
);
write(dir.path(), "001-user.md", b"first");
write(dir.path(), "notes.txt", b"ignored");
let msgs = assemble(dir.path(), None).unwrap();
assert_eq!(msgs.len(), 2);
assert_eq!(msgs[0].content, vec![Content::Text("first".into())]);
assert_eq!(msgs[1].role, Role::Assistant);
assert_eq!(msgs[1].content, vec![Content::Text("second".into())]);
}
fn tool_result(id: &str) -> Content {
Content::ToolResult {
tool_use_id: id.into(),
content: vec![Content::Text("out".into())],
is_error: false,
}
}
fn summary_rules() -> crate::config::manifest::RoleRules {
crate::config::manifest::RoleRules {
pinned: vec![],
order: vec!["summary/**".into()],
budget_tokens: 100,
overflow: crate::config::manifest::OverflowPolicy::Drop,
}
}
#[test]
fn body_blocks_lead_the_history_and_group_with_the_first_user_entry() {
let dir = TempDir::new().unwrap();
std::fs::create_dir_all(dir.path().join("summary")).unwrap();
std::fs::write(dir.path().join("summary/001.md"), "the summary").unwrap();
write(dir.path(), "001-user.md", b"go");
let msgs = assemble(dir.path(), Some(&summary_rules())).unwrap();
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0].role, Role::User);
assert_eq!(msgs[0].content.len(), 2);
assert!(
matches!(&msgs[0].content[0], Content::Text(t) if t.contains("summary/001.md") && t.contains("the summary"))
);
assert!(matches!(&msgs[0].content[1], Content::Text(t) if t == "go"));
}
#[test]
fn transcript_composes_the_tail_alone() {
let dir = TempDir::new().unwrap();
std::fs::create_dir_all(dir.path().join("summary")).unwrap();
std::fs::write(dir.path().join("summary/001.md"), "the summary").unwrap();
write(dir.path(), "001-user.md", b"go");
let msgs = transcript(dir.path()).unwrap();
assert_eq!(msgs.len(), 1);
assert_eq!(msgs[0].content, vec![Content::Text("go".into())]);
}