Skip to main content

max / audiofiles

4.7 KB · 146 lines History Blame Raw
1 //! Open, pragma, version-guard and transaction tests for the wrapper.
2 //!
3 //! Extracted from the former `db.rs` inline test module.
4
5 use crate::db::*;
6
7 #[test]
8 fn file_db_applies_performance_pragmas() {
9 let dir = tempfile::tempdir().unwrap();
10 let db = Database::open(dir.path().join("audiofiles.db")).unwrap();
11
12 let journal: String = db
13 .conn()
14 .query_row("PRAGMA journal_mode", [], |r| r.get(0))
15 .unwrap();
16 assert_eq!(journal.to_lowercase(), "wal");
17
18 // synchronous: 0=OFF, 1=NORMAL, 2=FULL. We want NORMAL under WAL.
19 let synchronous: i64 = db
20 .conn()
21 .query_row("PRAGMA synchronous", [], |r| r.get(0))
22 .unwrap();
23 assert_eq!(synchronous, 1, "synchronous should be NORMAL");
24
25 let temp_store: i64 = db
26 .conn()
27 .query_row("PRAGMA temp_store", [], |r| r.get(0))
28 .unwrap();
29 assert_eq!(temp_store, 2, "temp_store should be MEMORY");
30 }
31
32 /// A vault written by a newer audiofiles is refused, not opened.
33 ///
34 /// The bug this pins: `migrate()` only ever compared `version < target`, so
35 /// a vault ahead of the build applied nothing, returned `Ok`, and left every
36 /// query below running against a schema this code has never seen. Two things
37 /// are asserted, and the second is the one that matters: the open fails, AND
38 /// it fails without having touched the database, so an older build cannot
39 /// half-write a newer vault on its way to giving up.
40 #[test]
41 fn open_refuses_a_vault_from_a_newer_audiofiles() {
42 let dir = tempfile::tempdir().unwrap();
43 let path = dir.path().join("audiofiles.db");
44
45 Database::open(&path).unwrap();
46 let ahead = SCHEMA_VERSION + 1;
47 {
48 let conn = Connection::open(&path).unwrap();
49 conn.execute_batch(&format!("PRAGMA user_version = {ahead}"))
50 .unwrap();
51 }
52
53 let Err(err) = Database::open(&path) else {
54 panic!("a newer vault must not open");
55 };
56 let DbError::VaultTooNew { found, supported } = err else {
57 panic!("expected VaultTooNew, got {err:?}");
58 };
59 assert_eq!(found, ahead);
60 assert_eq!(supported, SCHEMA_VERSION);
61 // The message is the whole remedy the user gets, so it has to say which
62 // side is old rather than printing two bare numbers.
63 let text = err.to_string();
64 assert!(text.contains("newer version of audiofiles"), "{text}");
65
66 let after: i32 = Connection::open(&path)
67 .unwrap()
68 .query_row("PRAGMA user_version", [], |row| row.get(0))
69 .unwrap();
70 assert_eq!(after, ahead, "a refused open must not rewrite the vault");
71 }
72
73 /// The boundary is `>`, not `>=`: a vault at exactly this build's version is
74 /// the ordinary case and opens with no migration run. Guards against a
75 /// one-off that would refuse every up-to-date vault.
76 #[test]
77 fn open_accepts_a_vault_at_the_current_version() {
78 let dir = tempfile::tempdir().unwrap();
79 let path = dir.path().join("audiofiles.db");
80
81 Database::open(&path).unwrap();
82 let db = Database::open(&path).expect("a current vault opens");
83 let version: i32 = db
84 .conn()
85 .query_row("PRAGMA user_version", [], |row| row.get(0))
86 .unwrap();
87 assert_eq!(version, SCHEMA_VERSION);
88 }
89
90 #[test]
91 fn foreign_keys_enforced() {
92 let db = Database::open_in_memory().unwrap();
93 // Inserting a vfs_node referencing a non-existent vfs should fail
94 let result = db.conn().execute(
95 "INSERT INTO vfs_nodes (vfs_id, name, node_type, created_at) VALUES (999, 'test', 'directory', 0)",
96 [],
97 );
98 assert!(result.is_err());
99 }
100
101 #[test]
102 fn transaction_commits_on_success() {
103 let db = Database::open_in_memory().unwrap();
104 db.transaction(|_tx| {
105 db.conn().execute(
106 "INSERT INTO user_config (key, value) VALUES ('test_key', 'test_value')",
107 [],
108 )?;
109 Ok(())
110 })
111 .unwrap();
112
113 let val: String = db
114 .conn()
115 .query_row(
116 "SELECT value FROM user_config WHERE key = 'test_key'",
117 [],
118 |row| row.get(0),
119 )
120 .unwrap();
121 assert_eq!(val, "test_value");
122 }
123
124 #[test]
125 fn transaction_rolls_back_on_error() {
126 let db = Database::open_in_memory().unwrap();
127 let result: Result<(), DbError> = db.transaction(|_tx| {
128 db.conn().execute(
129 "INSERT INTO user_config (key, value) VALUES ('rollback_key', 'val')",
130 [],
131 )?;
132 Err(DbError::Sqlite(rusqlite::Error::QueryReturnedNoRows))
133 });
134 assert!(result.is_err());
135
136 let count: i64 = db
137 .conn()
138 .query_row(
139 "SELECT COUNT(*) FROM user_config WHERE key = 'rollback_key'",
140 [],
141 |row| row.get(0),
142 )
143 .unwrap();
144 assert_eq!(count, 0);
145 }
146