Codex Handbook
message-history/src/tests.rs 220 lines
use super::*;use codex_config::types::History;use pretty_assertions::assert_eq;use std::fs::File;use std::io::Write;use tempfile::TempDir;#[tokio::test]async fn lookup_reads_history_entries() {    let temp_dir = TempDir::new().expect("create temp dir");    let history_path = temp_dir.path().join(HISTORY_FILENAME);    let entries = vec![        HistoryEntry {            session_id: "first-session".to_string(),            ts: 1,            text: "first".to_string(),        },        HistoryEntry {            session_id: "second-session".to_string(),            ts: 2,            text: "second".to_string(),        },    ];    let mut file = File::create(&history_path).expect("create history file");    for entry in &entries {        writeln!(            file,            "{}",            serde_json::to_string(entry).expect("serialize history entry")        )        .expect("write history entry");    }    let (log_id, count) = history_metadata_for_file(&history_path).await;    assert_eq!(count, entries.len());    let second_entry = lookup_history_entry(&history_path, log_id, /*offset*/ 1)        .expect("fetch second history entry");    assert_eq!(second_entry, entries[1]);}#[tokio::test]async fn history_metadata_counts_newlines_across_read_boundaries() {    let temp_dir = TempDir::new().expect("create temp dir");    let history_path = temp_dir.path().join(HISTORY_FILENAME);    let mut contents = vec![b'x'; 3 * HISTORY_READ_BUFFER_SIZE + 1];    let newline_offsets = [        0,        HISTORY_READ_BUFFER_SIZE - 1,        HISTORY_READ_BUFFER_SIZE,        2 * HISTORY_READ_BUFFER_SIZE,        contents.len() - 2,    ];    for offset in newline_offsets {        contents[offset] = b'\n';    }    std::fs::write(&history_path, contents).expect("write history file");    let (_, count) = history_metadata_for_file(&history_path).await;    assert_eq!(count, newline_offsets.len());}#[tokio::test]async fn lookup_uses_stable_log_id_after_appends() {    let temp_dir = TempDir::new().expect("create temp dir");    let history_path = temp_dir.path().join(HISTORY_FILENAME);    let initial = HistoryEntry {        session_id: "first-session".to_string(),        ts: 1,        text: "first".to_string(),    };    let appended = HistoryEntry {        session_id: "second-session".to_string(),        ts: 2,        text: "second".to_string(),    };    let mut file = File::create(&history_path).expect("create history file");    writeln!(        file,        "{}",        serde_json::to_string(&initial).expect("serialize initial entry")    )    .expect("write initial entry");    let (log_id, count) = history_metadata_for_file(&history_path).await;    assert_eq!(count, 1);    let mut append = std::fs::OpenOptions::new()        .append(true)        .open(&history_path)        .expect("open history file for append");    writeln!(        append,        "{}",        serde_json::to_string(&appended).expect("serialize appended entry")    )    .expect("append history entry");    let fetched = lookup_history_entry(&history_path, log_id, /*offset*/ 1)        .expect("lookup appended history entry");    assert_eq!(fetched, appended);}#[tokio::test]async fn append_entry_trims_history_when_beyond_max_bytes() {    let codex_home = TempDir::new().expect("create temp dir");    let mut history = History::default();    let mut config = HistoryConfig::new(codex_home.path(), &history);    let conversation_id = "conversation-id";    let entry_one = "a".repeat(200);    let entry_two = "b".repeat(200);    let history_path = codex_home.path().join("history.jsonl");    append_entry(&entry_one, &conversation_id, &config)        .await        .expect("write first entry");    let first_len = std::fs::metadata(&history_path).expect("metadata").len();    let limit_bytes = first_len + 10;    history.max_bytes = Some(usize::try_from(limit_bytes).expect("limit should fit into usize"));    config = HistoryConfig::new(codex_home.path(), &history);    append_entry(&entry_two, &conversation_id, &config)        .await        .expect("write second entry");    let contents = std::fs::read_to_string(&history_path).expect("read history");    let entries = contents        .lines()        .map(|line| serde_json::from_str::<HistoryEntry>(line).expect("parse entry"))        .collect::<Vec<HistoryEntry>>();    assert_eq!(        entries.len(),        1,        "only one entry left because entry_one should be evicted"    );    assert_eq!(entries[0].text, entry_two);    assert!(std::fs::metadata(&history_path).expect("metadata").len() <= limit_bytes);}#[tokio::test]async fn append_entry_trims_history_to_soft_cap() {    let codex_home = TempDir::new().expect("create temp dir");    let mut history = History::default();    let mut config = HistoryConfig::new(codex_home.path(), &history);    let conversation_id = "conversation-id";    let short_entry = "a".repeat(200);    let long_entry = "b".repeat(400);    let history_path = codex_home.path().join("history.jsonl");    append_entry(&short_entry, &conversation_id, &config)        .await        .expect("write first entry");    let short_entry_len = std::fs::metadata(&history_path).expect("metadata").len();    append_entry(&long_entry, &conversation_id, &config)        .await        .expect("write second entry");    let two_entry_len = std::fs::metadata(&history_path).expect("metadata").len();    let long_entry_len = two_entry_len        .checked_sub(short_entry_len)        .expect("second entry length should be larger than first entry length");    history.max_bytes = Some(        usize::try_from((2 * long_entry_len) + (short_entry_len / 2))            .expect("max bytes should fit into usize"),    );    config = HistoryConfig::new(codex_home.path(), &history);    append_entry(&long_entry, &conversation_id, &config)        .await        .expect("write third entry");    let contents = std::fs::read_to_string(&history_path).expect("read history");    let entries = contents        .lines()        .map(|line| serde_json::from_str::<HistoryEntry>(line).expect("parse entry"))        .collect::<Vec<HistoryEntry>>();    assert_eq!(entries.len(), 1);    assert_eq!(entries[0].text, long_entry);    let pruned_len = std::fs::metadata(&history_path).expect("metadata").len();    let max_bytes = config.max_bytes.expect("max bytes should be configured") as u64;    assert!(pruned_len <= max_bytes);    let soft_cap_bytes = ((max_bytes as f64) * HISTORY_SOFT_CAP_RATIO)        .floor()        .clamp(1.0, max_bytes as f64) as u64;    let len_without_first = 2 * long_entry_len;    assert!(        len_without_first <= max_bytes,        "dropping only the first entry would satisfy the hard cap"    );    assert!(        len_without_first > soft_cap_bytes,        "soft cap should require more aggressive trimming than the hard cap"    );    assert_eq!(pruned_len, long_entry_len);    assert!(pruned_len <= soft_cap_bytes.max(long_entry_len));}