use std::borrow::Cow; use sqlx::Row; use sqlx::migrate::Migration; use sqlx::migrate::Migrator; use sqlx::sqlite::SqlitePoolOptions; use super::STATE_MIGRATOR; use super::repair_legacy_recency_migration_version; fn migrator_through(version: i64) -> Migrator { Migrator { migrations: Cow::Owned( STATE_MIGRATOR .migrations .iter() .filter(|migration| migration.version <= version) .cloned() .collect(), ), ignore_missing: STATE_MIGRATOR.ignore_missing, locking: STATE_MIGRATOR.locking, table_name: STATE_MIGRATOR.table_name.clone(), create_schemas: STATE_MIGRATOR.create_schemas.clone(), no_tx: STATE_MIGRATOR.no_tx, } } #[tokio::test] async fn recency_migration_backfills_and_seeds_old_binary_inserts() { let pool = SqlitePoolOptions::new() .max_connections(1) .connect("sqlite::memory:") .await .expect("in-memory database should open"); migrator_through(/*version*/ 37) .run(&pool) .await .expect("pre-recency migrations should apply"); sqlx::query( r#" INSERT INTO threads ( id, rollout_path, created_at, updated_at, created_at_ms, updated_at_ms, source, model_provider, cwd, title, sandbox_policy, approval_mode ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "#, ) .bind("00000000-0000-0000-0000-000000000001") .bind("/tmp/first.jsonl") .bind(1_700_000_000_i64) .bind(1_700_000_100_i64) .bind(1_700_000_000_123_i64) .bind(1_700_000_100_456_i64) .bind("cli") .bind("openai") .bind("/tmp") .bind("") .bind("read-only") .bind("on-request") .execute(&pool) .await .expect("legacy row should insert"); STATE_MIGRATOR .run(&pool) .await .expect("recency migration should apply"); let backfilled = sqlx::query( "SELECT updated_at, updated_at_ms, recency_at, recency_at_ms FROM threads WHERE id = ?", ) .bind("00000000-0000-0000-0000-000000000001") .fetch_one(&pool) .await .expect("backfilled row should load"); assert_eq!(backfilled.get::("recency_at"), 1_700_000_100); assert_eq!(backfilled.get::("recency_at_ms"), 1_700_000_100_456); sqlx::query( r#" INSERT INTO threads ( id, rollout_path, created_at, updated_at, created_at_ms, updated_at_ms, source, model_provider, cwd, title, sandbox_policy, approval_mode ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "#, ) .bind("00000000-0000-0000-0000-000000000002") .bind("/tmp/second.jsonl") .bind(1_700_000_200_i64) .bind(1_700_000_300_i64) .bind(1_700_000_200_123_i64) .bind(1_700_000_300_456_i64) .bind("cli") .bind("openai") .bind("/tmp") .bind("") .bind("read-only") .bind("on-request") .execute(&pool) .await .expect("old-binary row should insert"); let seeded = sqlx::query("SELECT recency_at, recency_at_ms FROM threads WHERE id = ?") .bind("00000000-0000-0000-0000-000000000002") .fetch_one(&pool) .await .expect("old-binary row should load"); assert_eq!(seeded.get::("recency_at"), 1_700_000_300); assert_eq!(seeded.get::("recency_at_ms"), 1_700_000_300_456); } #[tokio::test] async fn repairs_recency_migration_that_was_applied_as_version_38() { let pool = SqlitePoolOptions::new() .max_connections(1) .connect("sqlite::memory:") .await .expect("in-memory database should open"); migrator_through(/*version*/ 37) .run(&pool) .await .expect("pre-recency migrations should apply"); let recency_migration = STATE_MIGRATOR .migrations .iter() .find(|migration| migration.version == 39) .expect("recency migration should exist"); let mut legacy_migrations = STATE_MIGRATOR .migrations .iter() .filter(|migration| migration.version <= 37) .cloned() .collect::>(); legacy_migrations.push(Migration::new( 38, recency_migration.description.clone(), recency_migration.migration_type, recency_migration.sql.clone(), recency_migration.no_tx, )); let legacy_recency_migrator = Migrator::with_migrations(legacy_migrations); legacy_recency_migrator .run(&pool) .await .expect("legacy recency migration should apply as version 38"); repair_legacy_recency_migration_version(&pool, &STATE_MIGRATOR) .await .expect("legacy migration history should be repaired"); STATE_MIGRATOR .run(&pool) .await .expect("current migrations should apply after repair"); let applied = sqlx::query( "SELECT version, checksum FROM _sqlx_migrations WHERE version >= 38 ORDER BY version", ) .fetch_all(&pool) .await .expect("applied migrations should load") .into_iter() .map(|row| { ( row.get::("version"), row.get::, _>("checksum"), ) }) .collect::>(); let expected = STATE_MIGRATOR .migrations .iter() .filter(|migration| migration.version >= 38) .map(|migration| (migration.version, migration.checksum.to_vec())) .collect::>(); assert_eq!(applied, expected); }