use super::{AGENT, write_msg};
use crate::transcript::{Block, EntryKind, build};
use tempfile::tempdir;
fn kind_of(name: &str, bytes: &[u8]) -> EntryKind {
let dir = tempdir().unwrap();
write_msg(dir.path(), name, bytes);
let mut t = build(dir.path(), AGENT);
assert_eq!(t.entries.len(), 1, "one entry expected for {name}");
t.entries.remove(0).kind
}
#[test]
fn delivered_md_strips_the_deposit_envelope_off_the_body() {
assert_eq!(
kind_of(
"001-user.md",
b"---\nfrom: user\ndeposited_at: 2026-08-02T04:00:00Z\n---\nis this thing on?\n"
),
EntryKind::Delivered {
sender: "user".into(),
epitaph: None,
body: "is this thing on?\n".into()
}
);
}
#[test]
fn delivered_md_without_an_envelope_is_the_whole_file() {
assert_eq!(
kind_of("001-alice.md", b"hi there\n"),
EntryKind::Delivered {
sender: "alice".into(),
epitaph: None,
body: "hi there\n".into()
}
);
}
#[test]
fn delivered_result_message_with_no_content_has_an_empty_body() {
assert_eq!(
kind_of(
"004-kid.md",
b"---\nfrom: kid\ndeposited_at: t\nepitaph: died\nterminal_ref: sha\n---\n"
),
EntryKind::Delivered {
sender: "kid".into(),
epitaph: Some(crate::inboxview::Epitaph::Died),
body: String::new()
}
);
}
#[test]
fn delivered_sender_keeps_internal_hyphens() {
let EntryKind::Delivered { sender, .. } = kind_of("002-claude-fable.md", b"x") else {
panic!("expected delivered");
};
assert_eq!(sender, "claude-fable");
}
#[test]
fn model_bare_block_array_parses_text_and_thinking() {
let k = kind_of(
"001-opus.json",
br#"[{"type":"text","text":"hello"},{"type":"thinking","thinking":"hmm"}]"#,
);
let EntryKind::Model {
model_id,
blocks,
usage,
} = k
else {
panic!("expected model");
};
assert_eq!(model_id, "opus");
assert_eq!(
blocks,
vec![Block::Text("hello".into()), Block::Thinking("hmm".into())]
);
assert!(usage.is_empty(), "the legacy bare array carries no usage");
}
#[test]
fn model_usage_sibling_is_read_verbatim_and_non_counters_are_skipped() {
let k = kind_of(
"001-m.json",
br#"{"content":[{"type":"text","text":"hi"}],"usage":{"input_tokens":5,"output_tokens":3,"service_tier":"standard"}}"#,
);
let EntryKind::Model { usage, .. } = k else {
panic!("expected model");
};
assert_eq!(
usage.into_iter().collect::<Vec<_>>(),
vec![
("input_tokens".to_string(), 5),
("output_tokens".to_string(), 3)
]
);
}
#[test]
fn model_object_content_array_or_string() {
let arr = kind_of(
"001-m.json",
br#"{"role":"assistant","content":[{"type":"text","text":"x"}]}"#,
);
assert!(
matches!(arr, EntryKind::Model { blocks, .. } if blocks == vec![Block::Text("x".into())])
);
let s = kind_of("001-m.json", br#"{"content":"just text"}"#);
assert!(
matches!(s, EntryKind::Model { blocks, .. } if blocks == vec![Block::Text("just text".into())])
);
}
#[test]
fn model_object_without_content_has_no_blocks() {
let k = kind_of("001-m.json", br#"{"role":"assistant"}"#);
assert!(matches!(k, EntryKind::Model { blocks, .. } if blocks.is_empty()));
}
#[test]
fn model_tool_use_chip_summarizes_input() {
let k = kind_of(
"001-m.json",
br#"[{"type":"tool_use","id":"toolu_1","name":"Read","input":{"path":"/x"}}]"#,
);
let EntryKind::Model { blocks, .. } = k else {
panic!("expected model");
};
assert_eq!(
blocks,
vec![Block::ToolUse {
id: "toolu_1".into(),
name: "Read".into(),
input_summary: r#"{"path":"/x"}"#.into(),
}]
);
}
#[test]
fn model_tool_use_without_input_is_empty_summary() {
let k = kind_of(
"001-m.json",
br#"[{"type":"tool_use","id":"t","name":"Now"}]"#,
);
let EntryKind::Model { blocks, .. } = k else {
panic!("expected model");
};
assert!(matches!(&blocks[0], Block::ToolUse { input_summary, .. } if input_summary.is_empty()));
}
#[test]
fn model_tool_use_long_input_is_truncated() {
let big = "a".repeat(500);
let json = format!(r#"[{{"type":"tool_use","id":"t","name":"N","input":{{"k":"{big}"}}}}]"#);
let EntryKind::Model { blocks, .. } = kind_of("001-m.json", json.as_bytes()) else {
panic!("expected model");
};
let Block::ToolUse { input_summary, .. } = &blocks[0] else {
panic!("expected tool_use");
};
assert!(
input_summary.ends_with('…'),
"not truncated: {input_summary}"
);
}
#[test]
fn model_untyped_and_unknown_type_blocks_are_skipped() {
let k = kind_of(
"001-m.json",
br#"[{"foo":1},{"type":"image","x":1},{"type":"text","text":"y"}]"#,
);
assert!(
matches!(k, EntryKind::Model { blocks, .. } if blocks == vec![Block::Text("y".into())])
);
}
#[test]
fn model_invalid_json_falls_to_raw() {
assert_eq!(kind_of("001-m.json", b"not json"), EntryKind::Raw);
}
#[test]
fn tool_result_direct_fields() {
assert_eq!(
kind_of(
"001-tool.json",
br#"{"tool_use_id":"t","content":"done","is_error":false}"#
),
EntryKind::ToolResult {
tool_use_id: "t".into(),
content: "done".into(),
is_error: false,
}
);
}
#[test]
fn tool_result_error_flag_and_array_and_scalar_content() {
assert!(matches!(
kind_of(
"001-tool.json",
br#"{"tool_use_id":"t","content":"boom","is_error":true}"#
),
EntryKind::ToolResult { is_error: true, .. }
));
assert!(matches!(
kind_of("001-tool.json", br#"{"tool_use_id":"t","content":[{"type":"text","text":"a"},{"type":"text","text":"b"}]}"#),
EntryKind::ToolResult { content, .. } if content == "ab"
));
assert!(matches!(
kind_of("001-tool.json", br#"{"tool_use_id":"t","content":[{"type":"image","x":1}]}"#),
EntryKind::ToolResult { content, .. } if content == r#"{"type":"image","x":1}"#
));
assert!(matches!(
kind_of("001-tool.json", br#"{"tool_use_id":"t","content":42}"#),
EntryKind::ToolResult { content, .. } if content == "42"
));
}
#[test]
fn tool_result_absent_and_null_content_are_empty() {
assert!(matches!(
kind_of("001-tool.json", br#"{"tool_use_id":"t"}"#),
EntryKind::ToolResult { content, .. } if content.is_empty()
));
assert!(matches!(
kind_of("001-tool.json", br#"{"tool_use_id":"t","content":null}"#),
EntryKind::ToolResult { content, .. } if content.is_empty()
));
}
#[test]
fn tool_result_wrapped_in_content_array_is_found() {
assert!(matches!(
kind_of("001-tool.json", br#"{"role":"user","content":[{"tool_use_id":"t","content":"z"}]}"#),
EntryKind::ToolResult { tool_use_id, content, .. } if tool_use_id == "t" && content == "z"
));
}
#[test]
fn tool_json_without_result_block_is_raw() {
assert_eq!(kind_of("001-tool.json", br#"{"foo":1}"#), EntryKind::Raw);
assert_eq!(
kind_of("001-tool.json", br#"{"content":"x"}"#),
EntryKind::Raw
);
assert_eq!(kind_of("001-tool.json", b"nope"), EntryKind::Raw);
}
#[test]
fn unparseable_names_go_to_raw_bucket_not_dropped() {
let dir = tempdir().unwrap();
for n in [
"README",
"readme.md",
"-x.md",
"abc-x.md",
"003-.md",
"001-note.txt",
] {
write_msg(dir.path(), n, b"body");
}
let t = build(dir.path(), AGENT);
assert_eq!(t.entries.len(), 6, "none dropped");
assert!(t.entries.iter().all(|e| e.kind == EntryKind::Raw));
assert!(t.entries.iter().any(|e| e.name == "001-note.txt"));
}
#[test]
fn raw_entry_keeps_verbatim_bytes() {
let dir = tempdir().unwrap();
write_msg(dir.path(), "README", b"\x00\x01raw");
let t = build(dir.path(), AGENT);
assert_eq!(t.entries[0].raw, b"\x00\x01raw");
}