mobius 0.9.26

A small, modular Rust framework for building coding agents
Documentation
use super::*;

#[test]
fn open_rejects_a_nonempty_unversioned_database() {
    let workspace = tempfile::tempdir().expect("create workspace");
    let path = workspace.path().join("checkpoints.sqlite3");
    let connection = Connection::open(&path).expect("create unversioned database");
    connection
        .execute("CREATE TABLE legacy_state (value TEXT)", [])
        .expect("create legacy schema");
    drop(connection);

    let error = SqliteCheckpoint::new(path)
        .err()
        .expect("nonempty unversioned database must fail");

    assert_eq!(
        error.to_string(),
        "checkpoint error: unversioned SQLite database is not empty; expected schema version \
             6 (start with a fresh database)"
    );
}

#[tokio::test]
async fn load_completes_while_another_connection_holds_a_write_transaction() {
    let workspace = tempfile::tempdir().expect("create workspace");
    let store = Arc::new(
        SqliteCheckpoint::new(workspace.path().join("checkpoints.sqlite3"))
            .expect("open checkpoint database"),
    );
    let checkpoint = Checkpoint::empty("session");
    store
        .save(&checkpoint, &[], None)
        .await
        .expect("seed checkpoint");
    let (ready_tx, ready_rx) = oneshot::channel();
    let (release_tx, release_rx) = mpsc::channel();
    let writer = tokio::spawn({
        let store = Arc::clone(&store);
        async move {
            store
                .run(move |connection| {
                    let transaction =
                        connection.transaction_with_behavior(TransactionBehavior::Immediate)?;
                    ready_tx.send(()).expect("signal held transaction");
                    release_rx.recv().expect("release held transaction");
                    transaction.commit()?;
                    Ok(())
                })
                .await
        }
    });

    ready_rx.await.expect("wait for held transaction");
    let loaded = timeout(Duration::from_secs(1), store.load("session")).await;
    release_tx.send(()).expect("release held transaction");
    writer
        .await
        .expect("join held transaction")
        .expect("commit held transaction");

    assert_eq!(
        loaded
            .expect("reader blocked behind writer")
            .expect("load checkpoint"),
        Some(checkpoint)
    );
}

#[tokio::test]
async fn save_rejects_a_nonadvancing_sequence() {
    let workspace = tempfile::tempdir().expect("create workspace");
    let store = SqliteCheckpoint::new(workspace.path().join("checkpoints.sqlite3"))
        .expect("open checkpoint database");
    let mut checkpoint = Checkpoint::empty("session");
    checkpoint.sequence = 2;

    store
        .save(&checkpoint, &[], None)
        .await
        .expect("initial save");

    assert!(store.save(&checkpoint, &[], None).await.is_err());
    let mut older = checkpoint.clone();
    older.sequence = 1;
    assert!(store.save(&older, &[], None).await.is_err());
    assert_eq!(
        store.load("session").await.expect("load checkpoint"),
        Some(checkpoint)
    );
}