use super::*;
#[tokio::test]
async fn checkpoint_version_hard_rejects_previous_generations() {
let workspace = tempfile::tempdir().expect("create workspace");
let store = SqliteCheckpoint::new(workspace.path().join("checkpoints.sqlite3"))
.expect("open checkpoint database");
let mut checkpoint = checkpoint("session");
assert_eq!(checkpoint.version, 13);
for version in [9, 11, 12] {
checkpoint.version = version;
let error = store
.save(&checkpoint, &[], None)
.await
.expect_err("older checkpoint shape must fail");
assert!(
error
.to_string()
.contains(&format!("unsupported checkpoint version {version}"))
);
}
}
#[test]
fn open_hard_rejects_previous_schema_generations() {
for version in [6, 7, 8] {
let workspace = tempfile::tempdir().expect("create workspace");
let path = workspace.path().join("checkpoints.sqlite3");
drop(SqliteCheckpoint::new(&path).expect("create current database"));
let connection = Connection::open(&path).expect("open database");
connection
.pragma_update(None, "user_version", version)
.expect("set older schema version");
drop(connection);
let error = SqliteCheckpoint::new(path)
.err()
.expect("older session-context schema must fail");
assert_eq!(
error.to_string(),
format!(
"checkpoint error: unsupported SQLite schema version {version}; expected 9 \
(start with a fresh database)"
)
);
}
}
#[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 \
9 (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("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("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)
);
}