state/src/runtime/recovery_tests.rs
105 lines
use super::*;use crate::runtime::test_support::unique_temp_dir;use pretty_assertions::assert_eq;#[tokio::test]async fn backup_moves_only_requested_runtime_db_files_to_backup_folder() -> std::io::Result<()> { let sqlite_home = unique_temp_dir(); tokio::fs::create_dir_all(sqlite_home.as_path()).await?; let runtime_paths = super::super::runtime_db_paths(sqlite_home.as_path()); let mut expected_paths = Vec::new(); for db_path in runtime_paths.iter().map(|db| db.path.as_path()) { for path in sqlite_paths(db_path) { tokio::fs::write(path.as_path(), path.display().to_string()).await?; expected_paths.push(path); } } let failed_db_path = super::super::logs_db_path(sqlite_home.as_path()); let failed_paths = sqlite_paths(failed_db_path.as_path()); let backups = backup_runtime_db_for_fresh_start(failed_db_path.as_path()).await?; assert_eq!(backups.len(), failed_paths.len()); for path in &failed_paths { assert!(!tokio::fs::try_exists(path.as_path()).await?); } for path in expected_paths .iter() .filter(|path| !failed_paths.contains(path)) { assert!(tokio::fs::try_exists(path.as_path()).await?); } for backup in backups { assert!( backup .backup_path .starts_with(sqlite_home.join(BACKUP_DIR_NAME)) ); assert!(tokio::fs::try_exists(backup.backup_path.as_path()).await?); } Ok(())}#[tokio::test]async fn backup_replaces_blocking_sqlite_home_file() -> std::io::Result<()> { let temp_dir = unique_temp_dir(); tokio::fs::create_dir_all(temp_dir.as_path()).await?; let sqlite_home = temp_dir.join("sqlite-home"); tokio::fs::write(sqlite_home.as_path(), b"not-a-directory").await?; let backups = backup_runtime_db_for_fresh_start( super::super::state_db_path(sqlite_home.as_path()).as_path(), ) .await?; assert_eq!(backups.len(), 1); assert!(tokio::fs::metadata(sqlite_home.as_path()).await?.is_dir()); assert!( backups[0] .backup_path .starts_with(temp_dir.join(format!("sqlite-home.{BACKUP_DIR_NAME}"))) ); assert!(tokio::fs::try_exists(backups[0].backup_path.as_path()).await?); Ok(())}#[test]fn sqlite_error_detail_classifies_corruption_and_lock_errors() { assert!(sqlite_error_detail_is_corruption("file is not a database")); assert!(sqlite_error_detail_is_corruption( "error returned from database: (code: 11) database disk image is malformed" )); assert!(!sqlite_error_detail_is_corruption("database is locked")); assert!(sqlite_error_detail_is_lock("database is locked")); assert!(sqlite_error_detail_is_lock("database is busy"));}#[tokio::test]async fn runtime_db_path_for_corruption_error_returns_failed_database_path() -> std::io::Result<()>{ let sqlite_home = unique_temp_dir(); tokio::fs::create_dir_all(sqlite_home.as_path()).await?; let path = super::super::state_db_path(sqlite_home.as_path()); tokio::fs::write(path.as_path(), b"not sqlite").await?; let err = match super::super::StateRuntime::init(sqlite_home, "openai".to_string()).await { Ok(_) => panic!("malformed sqlite should fail to initialize"), Err(err) => err, }; assert_eq!(runtime_db_path_for_corruption_error(&err), Some(path)); Ok(())}#[test]fn runtime_db_path_for_corruption_error_ignores_corrupt_word_in_path() { let path = PathBuf::from("/tmp/sqlite_corrupt/state_5.sqlite"); let err = anyhow::Error::new(RuntimeDbInitError::new( "state DB", "open", path.as_path(), anyhow::anyhow!("permission denied"), )); assert_eq!(runtime_db_path_for_corruption_error(&err), None);}