//! Record-text resolution for the v0.10.0 promoted leaves + the duration formatter.
use super::*;
fn rec(line: &str) -> Record {
serde_json::from_str(line).unwrap()
}
#[test]
fn promoted_text_is_verbatim_for_queued_and_recap() {
let q = rec(
r#"{"type":"queue-operation","operation":"enqueue","timestamp":"t","sessionId":"s","content":"check the widget build"}"#,
);
// Both the promoted path and the shared raw-text entry return the verbatim content
// (the promoted arm is pinned directly: the D7 system fallback would also read
// `content`, so only a direct call proves the arm is live).
assert_eq!(
promoted_record_text(&q).as_deref(),
Some("check the widget build")
);
assert_eq!(
record_raw_text(&q).as_deref(),
Some("check the widget build")
);
let a = rec(
r#"{"type":"system","subtype":"away_summary","content":"Two checks finished.","uuid":"u","timestamp":"t"}"#,
);
assert_eq!(
promoted_record_text(&a).as_deref(),
Some("Two checks finished.")
);
assert_eq!(record_raw_text(&a).as_deref(), Some("Two checks finished."));
// A non-promoted record is untouched by the promoted path.
let u = rec(r#"{"type":"user","message":{"role":"user","content":"hi"}}"#);
assert_eq!(promoted_record_text(&u), None);
assert_eq!(record_raw_text(&u).as_deref(), Some("hi"));
}
#[test]
fn turn_duration_excerpt_lists_present_fields_only() {
let full = rec(
r#"{"type":"system","subtype":"turn_duration","durationMs":64911,"messageCount":908,"pendingBackgroundAgentCount":2,"uuid":"u","timestamp":"t"}"#,
);
assert_eq!(
promoted_record_text(&full).as_deref(),
Some("[turn duration: 1m 5s · durationMs=64911 messageCount=908 pendingBackgroundAgentCount=2]")
);
let bare = rec(r#"{"type":"system","subtype":"turn_duration","durationMs":1400,"uuid":"u"}"#);
assert_eq!(
promoted_record_text(&bare).as_deref(),
Some("[turn duration: 1s · durationMs=1400]")
);
// No numeric field at all: nothing fabricated.
let empty = rec(r#"{"type":"system","subtype":"turn_duration","uuid":"u"}"#);
assert_eq!(promoted_record_text(&empty), None);
// An absurd span (a turn straddling a resume gap) reads as days, not seconds.
let long =
rec(r#"{"type":"system","subtype":"turn_duration","durationMs":926676611,"uuid":"u"}"#);
assert!(
promoted_record_text(&long)
.unwrap()
.starts_with("[turn duration: 10d 17h ·"),
"{:?}",
promoted_record_text(&long)
);
}
#[test]
fn stop_hooks_excerpt_carries_the_ledger_and_commands() {
let sh = rec(
r#"{"type":"system","subtype":"stop_hook_summary","hookCount":2,"hookInfos":[{"command":"/x/a.sh","durationMs":9},{"command":"/x/b.sh"}],"hookErrors":[{"command":"/x/b.sh"}],"preventedContinuation":true,"uuid":"u","timestamp":"t"}"#,
);
assert_eq!(
promoted_record_text(&sh).as_deref(),
Some("[stop hooks: count=2 errors=1 prevented=true]\n/x/a.sh (9ms)\n/x/b.sh")
);
// Missing arrays degrade to zero, never a crash.
let bare = rec(r#"{"type":"system","subtype":"stop_hook_summary","uuid":"u"}"#);
assert_eq!(
promoted_record_text(&bare).as_deref(),
Some("[stop hooks: count=0 errors=0 prevented=false]")
);
}
#[test]
fn snapshot_and_delta_excerpts_name_paths_and_versions() {
let snap = rec(
r#"{"type":"file-history-snapshot","messageId":"m","snapshot":{"messageId":"m","trackedFileBackups":{"/z/b.md":{"version":1},"/a/f.txt":{"version":3,"backupFileName":"f.txt@v3"}},"timestamp":"2026-06-07T05:00:00.000Z"},"isSnapshotUpdate":true}"#,
);
assert_eq!(
promoted_record_text(&snap).as_deref(),
Some("[file-history snapshot at 2026-06-07T05:00:00.000Z: /a/f.txt@v3, /z/b.md@v1]")
);
let delta = rec(
r#"{"type":"file-history-delta","messageId":"m","trackingPath":"/a/f.txt","backup":{"backupFileName":"f.txt@v4","version":4},"timestamp":"2026-06-07T05:01:00.000Z"}"#,
);
assert_eq!(
promoted_record_text(&delta).as_deref(),
Some("[file-history delta at 2026-06-07T05:01:00.000Z: /a/f.txt@v4 backup=f.txt@v4]")
);
// A snapshot line with no snapshot object has nothing to render.
let none = rec(r#"{"type":"file-history-snapshot","messageId":"m"}"#);
assert_eq!(promoted_record_text(&none), None);
// A delta with no tracking path likewise.
let none = rec(r#"{"type":"file-history-delta","messageId":"m","timestamp":"t"}"#);
assert_eq!(promoted_record_text(&none), None);
}
#[test]
fn meta_system_renders_subtype_level_and_content() {
// String content with a level: the Remote Control disconnect shape.
let info = rec(
r#"{"type":"system","subtype":"informational","level":"warning","uuid":"i","timestamp":"t","content":"Remote Control disconnected - run /remote-control"}"#,
);
assert_eq!(
promoted_record_text(&info).as_deref(),
Some("[informational warning] Remote Control disconnected - run /remote-control")
);
// No level: the head is the bare subtype; a non-string content renders as its JSON.
let killed = rec(
r#"{"type":"system","subtype":"agents_killed","uuid":"k","timestamp":"t","content":{"count":2}}"#,
);
assert_eq!(
promoted_record_text(&killed).as_deref(),
Some(r#"[agents_killed] {"count":2}"#)
);
// No content at all: the head alone (still a searchable, addressable record).
let bare = rec(r#"{"type":"system","subtype":"api_error","uuid":"e","timestamp":"t"}"#);
assert_eq!(promoted_record_text(&bare).as_deref(), Some("[api_error]"));
// An empty level string is treated as absent.
let empty = rec(
r#"{"type":"system","subtype":"local_command","level":"","uuid":"l","timestamp":"t","content":"ran"}"#,
);
assert_eq!(
promoted_record_text(&empty).as_deref(),
Some("[local_command] ran")
);
}
#[test]
fn fmt_ms_picks_the_top_two_units() {
assert_eq!(fmt_ms(0), "0s");
assert_eq!(fmt_ms(499), "0s");
assert_eq!(fmt_ms(999), "1s");
assert_eq!(fmt_ms(12_000), "12s");
assert_eq!(fmt_ms(64_911), "1m 5s");
assert_eq!(fmt_ms(3_600_000), "1h 0m");
assert_eq!(fmt_ms(7_380_000), "2h 3m");
assert_eq!(fmt_ms(926_676_611), "10d 17h");
}
/// C-33: the boundary excerpt gains the harness's own SURVIVOR fields, and a legacy
/// boundary carrying only the four scalars still renders byte-for-byte what it always did.
#[test]
fn boundary_excerpt_keeps_the_legacy_four_scalar_form_byte_for_byte() {
let legacy = rec(
r#"{"type":"system","subtype":"compact_boundary","uuid":"cb1","timestamp":"t","content":"Conversation compacted","compactMetadata":{"trigger":"auto","preTokens":1000,"postTokens":200,"durationMs":50}}"#,
);
assert_eq!(
system_record_text(&legacy).as_deref(),
Some(
"Conversation compacted [compaction boundary: trigger=auto preTokens=1000 \
postTokens=200 durationMs=50]"
)
);
}
#[test]
fn boundary_excerpt_renders_every_survivor_field_when_present() {
let full = rec(
r#"{"type":"system","subtype":"compact_boundary","uuid":"cb1","timestamp":"t","logicalParentUuid":"lp1","content":"Conversation compacted","compactMetadata":{"trigger":"manual","preTokens":41041,"postTokens":8121,"durationMs":44736,"messagesSummarized":66,"cumulativeDroppedTokens":32920,"preservedSegment":{"headUuid":"aaaa1111-2222-4333-8444-555566667777","anchorUuid":"bbbb2222-3333-4444-8555-666677778888","tailUuid":"cccc3333-4444-4555-8666-777788889999"},"preservedMessages":{"anchorUuid":"bbbb2222-3333-4444-8555-666677778888","uuids":["a","b"],"allUuids":["a","b","c"]}}}"#,
);
assert_eq!(
system_record_text(&full).as_deref(),
Some(
"Conversation compacted [compaction boundary: trigger=manual preTokens=41041 \
postTokens=8121 durationMs=44736 messagesSummarized=66 \
cumulativeDroppedTokens=32920 preserved=2 uuids, 3 allUuids, anchor bbbb2222 \
segment=aaaa1111..cccc3333] [logicalParent=lp1]"
)
);
}
#[test]
fn boundary_excerpt_prints_only_the_survivor_parts_it_has() {
// preservedMessages without allUuids, preservedSegment with one end only.
let partial = rec(
r#"{"type":"system","subtype":"compact_boundary","uuid":"cb1","timestamp":"t","content":"Conversation compacted","compactMetadata":{"trigger":"auto","preservedMessages":{"uuids":["a"]},"preservedSegment":{"headUuid":"aaaaaaaabbbb"}}}"#,
);
assert_eq!(
system_record_text(&partial).as_deref(),
Some(
"Conversation compacted [compaction boundary: trigger=auto preserved=1 uuids \
segment=aaaaaaaa..]"
)
);
// Empty survivor objects contribute nothing (no `preserved=` / `segment=` noise).
let empty = rec(
r#"{"type":"system","subtype":"compact_boundary","uuid":"cb1","timestamp":"t","content":"Conversation compacted","compactMetadata":{"trigger":"auto","preservedMessages":{},"preservedSegment":{}}}"#,
);
assert_eq!(
system_record_text(&empty).as_deref(),
Some("Conversation compacted [compaction boundary: trigger=auto]")
);
// No compactMetadata at all: the content line stands alone, as before.
let bare = rec(
r#"{"type":"system","subtype":"compact_boundary","uuid":"cb1","timestamp":"t","content":"Conversation compacted"}"#,
);
assert_eq!(
system_record_text(&bare).as_deref(),
Some("Conversation compacted")
);
}