ralph-coder 0.2.1

An agentic code generation CLI powered by multiple LLM backends
Documentation
/// Checkpoint tests — sandboxed in TempDir.
use ralph::checkpoint::Checkpoint;
use ralph::config::{Config, SessionConfig};
use ralph::providers::Message;
use ralph::session::Session;
use ralph::tools::file_tools;
use tempfile::TempDir;

fn test_config(sessions_dir: &str) -> Config {
    let mut config = Config::default();
    config.session = SessionConfig {
        auto_resume: false,
        sessions_dir: Some(sessions_dir.to_string()),
        clean_after_days: 30,
    };
    config
}

fn make_session(workspace: &std::path::Path, config: &Config) -> Session {
    Session::create(workspace, "anthropic", "claude-sonnet-4-6", config).unwrap()
}

#[tokio::test]
async fn create_checkpoint_at_session_start() {
    let dir = TempDir::new().unwrap();
    let workspace = dir.path().join("ws");
    std::fs::create_dir_all(&workspace).unwrap();
    let sessions_dir = dir.path().join("sessions");
    let config = test_config(&sessions_dir.display().to_string());

    let session = make_session(&workspace, &config);
    let cp = Checkpoint::create(&session, "initial", &Default::default())
        .await
        .unwrap();
    assert_eq!(cp.meta.name, "initial");
    assert_eq!(cp.meta.turn, 0);
}

#[tokio::test]
async fn list_checkpoints_sorted_by_sequence() {
    let dir = TempDir::new().unwrap();
    let workspace = dir.path().join("ws");
    std::fs::create_dir_all(&workspace).unwrap();
    let sessions_dir = dir.path().join("sessions");
    let config = test_config(&sessions_dir.display().to_string());

    let session = make_session(&workspace, &config);
    Checkpoint::create(&session, "first", &Default::default())
        .await
        .unwrap();
    Checkpoint::create(&session, "second", &Default::default())
        .await
        .unwrap();
    Checkpoint::create(&session, "third", &Default::default())
        .await
        .unwrap();

    let list = Checkpoint::list(&session);
    assert_eq!(list.len(), 3);
    assert_eq!(list[0].name, "first");
    assert_eq!(list[1].name, "second");
    assert_eq!(list[2].name, "third");
}

#[tokio::test]
async fn find_checkpoint_by_name() {
    let dir = TempDir::new().unwrap();
    let workspace = dir.path().join("ws");
    std::fs::create_dir_all(&workspace).unwrap();
    let sessions_dir = dir.path().join("sessions");
    let config = test_config(&sessions_dir.display().to_string());

    let session = make_session(&workspace, &config);
    Checkpoint::create(&session, "my-checkpoint", &Default::default())
        .await
        .unwrap();

    let found = Checkpoint::find(&session, "my-checkpoint").unwrap();
    assert_eq!(found.meta.name, "my-checkpoint");
}

#[tokio::test]
async fn find_checkpoint_by_number() {
    let dir = TempDir::new().unwrap();
    let workspace = dir.path().join("ws");
    std::fs::create_dir_all(&workspace).unwrap();
    let sessions_dir = dir.path().join("sessions");
    let config = test_config(&sessions_dir.display().to_string());

    let session = make_session(&workspace, &config);
    Checkpoint::create(&session, "alpha", &Default::default())
        .await
        .unwrap();
    Checkpoint::create(&session, "beta", &Default::default())
        .await
        .unwrap();

    let found = Checkpoint::find(&session, "2").unwrap();
    assert_eq!(found.meta.name, "beta");
}

#[tokio::test]
async fn find_nonexistent_checkpoint_errors() {
    let dir = TempDir::new().unwrap();
    let workspace = dir.path().join("ws");
    std::fs::create_dir_all(&workspace).unwrap();
    let sessions_dir = dir.path().join("sessions");
    let config = test_config(&sessions_dir.display().to_string());

    let session = make_session(&workspace, &config);
    assert!(Checkpoint::find(&session, "ghost").is_err());
    assert!(Checkpoint::find(&session, "99").is_err());
}

#[tokio::test]
async fn checkpoint_snapshots_modified_files() {
    let dir = TempDir::new().unwrap();
    let workspace = dir.path().join("ws");
    std::fs::create_dir_all(&workspace).unwrap();
    let sessions_dir = dir.path().join("sessions");
    let config = test_config(&sessions_dir.display().to_string());

    // Write a file in the workspace and track it
    let file_path = workspace.join("src").join("lib.rs");
    file_tools::write_file(&file_path, "pub fn hello() {}").unwrap();

    let mut session = make_session(&workspace, &config);
    session.modified_files.push(file_path.clone());

    let cp = Checkpoint::create(&session, "with-files", &Default::default())
        .await
        .unwrap();
    assert_eq!(cp.meta.modified_files.len(), 1);
    assert!(cp.meta.modified_files[0].contains("lib.rs"));

    // Verify snapshot exists on disk
    let snapshot = session
        .checkpoints_dir()
        .join(&cp.meta.id)
        .join("files")
        .join("src")
        .join("lib.rs");
    assert!(snapshot.exists());
    let snapshot_content = std::fs::read_to_string(&snapshot).unwrap();
    assert_eq!(snapshot_content, "pub fn hello() {}");
}

#[tokio::test]
async fn checkpoint_preserves_messages() {
    let dir = TempDir::new().unwrap();
    let workspace = dir.path().join("ws");
    std::fs::create_dir_all(&workspace).unwrap();
    let sessions_dir = dir.path().join("sessions");
    let config = test_config(&sessions_dir.display().to_string());

    let mut session = make_session(&workspace, &config);
    session.messages.push(Message::user("do the thing"));
    session.messages.push(Message::assistant("done!"));
    session.flush().unwrap();

    let cp = Checkpoint::create(&session, "with-msgs", &Default::default())
        .await
        .unwrap();
    assert_eq!(cp.messages.len(), 2);
}