use super::line::clip_arg;
use super::*;
use tempfile::tempdir;
fn sample() -> OpEntry {
OpEntry {
ts: "2026-07-17T12:00:00Z".into(),
argv: vec!["bl".into(), "close".into(), "bl-4db6".into()],
cwd: "/home/mark/dev/brazen".into(),
exit: 0,
stdout: "ok".into(),
stderr: String::new(),
}
}
fn parsed(line: &[u8]) -> serde_json::Map<String, Value> {
assert_eq!(*line.last().unwrap(), b'\n', "line must end in newline");
serde_json::from_slice::<Value>(&line[..line.len() - 1])
.unwrap()
.as_object()
.unwrap()
.clone()
}
#[test]
fn build_line_serializes_all_fields_untruncated() {
let line = build_line(&sample());
assert!(line.len() <= CAP);
let obj = parsed(&line);
assert_eq!(obj["ts"], Value::from("2026-07-17T12:00:00Z"));
assert_eq!(obj["argv"], Value::from(vec!["bl", "close", "bl-4db6"]));
assert_eq!(obj["cwd"], Value::from("/home/mark/dev/brazen"));
assert_eq!(obj["exit"], Value::from(0));
assert_eq!(obj["stdout"], Value::from("ok"));
assert_eq!(obj["stderr"], Value::from(""));
assert!(!obj.contains_key("truncated"));
}
#[test]
fn build_line_boundary_exactly_cap_then_one_over() {
let base = build_line(&OpEntry {
stdout: String::new(),
..sample()
})
.len();
let at_cap = build_line(&OpEntry {
stdout: "a".repeat(CAP - base),
..sample()
});
assert_eq!(at_cap.len(), CAP);
assert!(!parsed(&at_cap).contains_key("truncated"));
let over = build_line(&OpEntry {
stdout: "a".repeat(CAP - base + 1),
..sample()
});
assert!(over.len() <= CAP);
let obj = parsed(&over);
assert_eq!(obj["truncated"], Value::Bool(true));
let kept = obj["stdout"].as_str().unwrap();
assert!(kept.len() < CAP - base + 1);
assert!(kept.bytes().all(|b| b == b'a'));
}
#[test]
fn build_line_truncates_huge_stdout_keeping_stderr() {
let line = build_line(&OpEntry {
stdout: "a".repeat(100_000),
stderr: "keep-me".into(),
..sample()
});
assert!(line.len() <= CAP);
let obj = parsed(&line);
assert_eq!(obj["truncated"], Value::Bool(true));
assert_eq!(obj["stderr"], Value::from("keep-me"));
let kept = obj["stdout"].as_str().unwrap();
assert!(!kept.is_empty() && kept.bytes().all(|b| b == b'a'));
}
#[test]
fn build_line_sacrifices_stdout_before_stderr() {
let line = build_line(&OpEntry {
stdout: "dropped-entirely".into(),
stderr: "b".repeat(100_000),
..sample()
});
assert!(line.len() <= CAP);
let obj = parsed(&line);
assert_eq!(obj["truncated"], Value::Bool(true));
assert_eq!(obj["stdout"], Value::from(""));
let kept = obj["stderr"].as_str().unwrap();
assert!(!kept.is_empty() && kept.bytes().all(|b| b == b'b'));
}
#[test]
fn build_line_fixed_fields_exceeding_cap_zero_the_outputs() {
let line = build_line(&OpEntry {
argv: vec!["x".repeat(5_000)],
stdout: "gone".into(),
stderr: "also-gone".into(),
..sample()
});
assert!(line.len() > CAP);
let obj = parsed(&line);
assert_eq!(obj["truncated"], Value::Bool(true));
assert_eq!(obj["stdout"], Value::from(""));
assert_eq!(obj["stderr"], Value::from(""));
assert_eq!(obj["argv"].as_array().unwrap().len(), 1);
}
#[test]
fn build_line_snaps_head_to_utf8_boundary() {
let line = build_line(&OpEntry {
stdout: "é".repeat(50_000),
stderr: "z".into(),
..sample()
});
assert!(line.len() <= CAP);
let obj = parsed(&line);
let kept = obj["stdout"].as_str().unwrap();
assert!(!kept.is_empty());
assert!(kept.chars().all(|c| c == 'é'));
}
#[test]
fn append_then_tail_round_trips_in_order() {
let dir = tempdir().unwrap();
let root = dir.path();
let first = sample();
let second = OpEntry {
argv: vec!["lernie".into(), "scan".into()],
stdout: "summary".into(),
..sample()
};
append(root, &first).unwrap();
append(root, &second).unwrap();
assert_eq!(tail(root, 10), vec![first, second.clone()]);
assert_eq!(tail(root, 1), vec![second]);
}
#[test]
fn append_creates_missing_state_dirs() {
let dir = tempdir().unwrap();
let root = dir.path().join("nested").join("state");
append(&root, &sample()).unwrap();
assert_eq!(tail(&root, 5), vec![sample()]);
}
#[test]
fn tail_of_missing_file_is_empty() {
let dir = tempdir().unwrap();
assert!(tail(&dir.path().join("absent"), 5).is_empty());
}
#[test]
fn tail_skips_corrupt_and_non_object_lines() {
let dir = tempdir().unwrap();
let root = dir.path();
let path = root.join(FILENAME);
let good = build_line(&sample());
let mut bytes = good.clone();
bytes.extend_from_slice(b"{not valid json\n"); bytes.extend_from_slice(b"42\n"); bytes.extend_from_slice(b"\n"); bytes.extend_from_slice(&[0xff, 0xfe, b'\n']); bytes.extend_from_slice(&good); fs::write(&path, &bytes).unwrap();
let got = tail(root, 10);
assert_eq!(got, vec![sample(), sample()]);
}
#[test]
fn parse_line_defaults_absent_and_mistyped_fields() {
let dir = tempdir().unwrap();
let root = dir.path();
let path = root.join(FILENAME);
fs::write(&path, b"{}\n{\"argv\":[\"a\",5,\"b\"],\"exit\":\"nope\"}\n").unwrap();
let got = tail(root, 10);
assert_eq!(
got,
vec![
OpEntry {
ts: String::new(),
argv: vec![],
cwd: String::new(),
exit: 0,
stdout: String::new(),
stderr: String::new(),
},
OpEntry {
ts: String::new(),
argv: vec!["a".into(), "b".into()],
cwd: String::new(),
exit: 0,
stdout: String::new(),
stderr: String::new(),
},
]
);
}
#[test]
fn synthetic_failure_encodes_the_intended_argv_and_stderr() {
let e = OpEntry::synthetic_failure(
"TS".into(),
vec!["lernie".into(), "prompt".into()],
"/proj".into(),
"No such file or directory".into(),
);
assert_eq!(e.exit, SYNTHETIC_EXIT);
assert_eq!(e.argv, vec!["lernie".to_string(), "prompt".to_string()]);
assert_eq!(e.cwd, "/proj");
assert!(e.stdout.is_empty());
assert_eq!(e.stderr, "No such file or directory");
}
#[test]
fn step_failure_encodes_the_yog_step_argv() {
let e = OpEntry::step_failure("TS".into(), "mint", String::new(), "pool exhausted".into());
assert_eq!(e.argv, vec![YOG_STEP.to_string(), "mint".to_string()]);
assert_eq!(e.exit, SYNTHETIC_EXIT);
assert_eq!(e.stderr, "pool exhausted");
}
#[test]
fn clip_arg_returns_short_verbatim_and_marks_a_long_one() {
assert_eq!(clip_arg("goal", 16), "goal");
let clipped = clip_arg(&"x".repeat(100), 10);
assert!(clipped.starts_with(&"x".repeat(10)));
assert!(
clipped.contains("[+90 bytes elided]"),
"the marker names the dropped byte count"
);
}
#[test]
fn clip_goal_leaves_a_fitting_entry_untouched() {
let e = sample();
assert_eq!(clip_goal(&e), e);
}
#[test]
fn clip_goal_on_empty_argv_is_a_no_op() {
let e = OpEntry {
argv: vec![],
..sample()
};
assert_eq!(clip_goal(&e), e);
}
#[test]
fn clip_goal_holds_cap_after_json_escape() {
let e = OpEntry {
argv: vec![
"lernie".into(),
"prompt".into(),
"/ws".into(),
"\u{1}".repeat(9000),
],
..sample()
};
assert!(build_line(&e).len() > CAP, "the raw goal overflows the cap");
let clipped = clip_goal(&e);
assert!(
build_line(&clipped).len() <= CAP,
"serialized line ≤ CAP post-escape"
);
assert!(
clipped.argv.last().unwrap().contains("bytes elided"),
"the clipped goal carries the elision marker"
);
assert_eq!(&clipped.argv[..3], &e.argv[..3]);
}