//! Open, pragma, version-guard and transaction tests for the wrapper. //! //! Extracted from the former `db.rs` inline test module. use crate::db::*; #[test] fn file_db_applies_performance_pragmas() { let dir = tempfile::tempdir().unwrap(); let db = Database::open(dir.path().join("audiofiles.db")).unwrap(); let journal: String = db .conn() .query_row("PRAGMA journal_mode", [], |r| r.get(0)) .unwrap(); assert_eq!(journal.to_lowercase(), "wal"); // synchronous: 0=OFF, 1=NORMAL, 2=FULL. We want NORMAL under WAL. let synchronous: i64 = db .conn() .query_row("PRAGMA synchronous", [], |r| r.get(0)) .unwrap(); assert_eq!(synchronous, 1, "synchronous should be NORMAL"); let temp_store: i64 = db .conn() .query_row("PRAGMA temp_store", [], |r| r.get(0)) .unwrap(); assert_eq!(temp_store, 2, "temp_store should be MEMORY"); } /// A vault written by a newer audiofiles is refused, not opened. /// /// The bug this pins: `migrate()` only ever compared `version < target`, so /// a vault ahead of the build applied nothing, returned `Ok`, and left every /// query below running against a schema this code has never seen. Two things /// are asserted, and the second is the one that matters: the open fails, AND /// it fails without having touched the database, so an older build cannot /// half-write a newer vault on its way to giving up. #[test] fn open_refuses_a_vault_from_a_newer_audiofiles() { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("audiofiles.db"); Database::open(&path).unwrap(); let ahead = SCHEMA_VERSION + 1; { let conn = Connection::open(&path).unwrap(); conn.execute_batch(&format!("PRAGMA user_version = {ahead}")) .unwrap(); } let Err(err) = Database::open(&path) else { panic!("a newer vault must not open"); }; let DbError::VaultTooNew { found, supported } = err else { panic!("expected VaultTooNew, got {err:?}"); }; assert_eq!(found, ahead); assert_eq!(supported, SCHEMA_VERSION); // The message is the whole remedy the user gets, so it has to say which // side is old rather than printing two bare numbers. let text = err.to_string(); assert!(text.contains("newer version of audiofiles"), "{text}"); let after: i32 = Connection::open(&path) .unwrap() .query_row("PRAGMA user_version", [], |row| row.get(0)) .unwrap(); assert_eq!(after, ahead, "a refused open must not rewrite the vault"); } /// The boundary is `>`, not `>=`: a vault at exactly this build's version is /// the ordinary case and opens with no migration run. Guards against a /// one-off that would refuse every up-to-date vault. #[test] fn open_accepts_a_vault_at_the_current_version() { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("audiofiles.db"); Database::open(&path).unwrap(); let db = Database::open(&path).expect("a current vault opens"); let version: i32 = db .conn() .query_row("PRAGMA user_version", [], |row| row.get(0)) .unwrap(); assert_eq!(version, SCHEMA_VERSION); } #[test] fn foreign_keys_enforced() { let db = Database::open_in_memory().unwrap(); // Inserting a vfs_node referencing a non-existent vfs should fail let result = db.conn().execute( "INSERT INTO vfs_nodes (vfs_id, name, node_type, created_at) VALUES (999, 'test', 'directory', 0)", [], ); assert!(result.is_err()); } #[test] fn transaction_commits_on_success() { let db = Database::open_in_memory().unwrap(); db.transaction(|_tx| { db.conn().execute( "INSERT INTO user_config (key, value) VALUES ('test_key', 'test_value')", [], )?; Ok(()) }) .unwrap(); let val: String = db .conn() .query_row( "SELECT value FROM user_config WHERE key = 'test_key'", [], |row| row.get(0), ) .unwrap(); assert_eq!(val, "test_value"); } #[test] fn transaction_rolls_back_on_error() { let db = Database::open_in_memory().unwrap(); let result: Result<(), DbError> = db.transaction(|_tx| { db.conn().execute( "INSERT INTO user_config (key, value) VALUES ('rollback_key', 'val')", [], )?; Err(DbError::Sqlite(rusqlite::Error::QueryReturnedNoRows)) }); assert!(result.is_err()); let count: i64 = db .conn() .query_row( "SELECT COUNT(*) FROM user_config WHERE key = 'rollback_key'", [], |row| row.get(0), ) .unwrap(); assert_eq!(count, 0); }