backup_export_paths.rs (10363B)
1 use std::collections::VecDeque; 2 use std::sync::Mutex; 3 4 use radroots_replica_db::backup::{ 5 DATABASE_BACKUP_VERSION, DatabaseBackup, REPLICA_DB_VERSION, SchemaEntry, TableData, 6 export_database_backup, export_database_backup_json, restore_database_backup, 7 }; 8 use radroots_replica_db::export::export_manifest; 9 use radroots_replica_db::{ExecOutcome, SqlError, SqlExecutor}; 10 use serde_json::{Map, Value, json}; 11 12 struct PatternExecutor { 13 query_rules: Vec<(String, String)>, 14 fail_query_contains: Option<String>, 15 fail_exec_contains: Option<String>, 16 fail_begin: bool, 17 fail_commit: bool, 18 begin_count: Mutex<usize>, 19 commit_count: Mutex<usize>, 20 rollback_count: Mutex<usize>, 21 query_queue: Mutex<VecDeque<Result<String, SqlError>>>, 22 exec_queue: Mutex<VecDeque<Result<ExecOutcome, SqlError>>>, 23 } 24 25 impl PatternExecutor { 26 fn new() -> Self { 27 Self { 28 query_rules: Vec::new(), 29 fail_query_contains: None, 30 fail_exec_contains: None, 31 fail_begin: false, 32 fail_commit: false, 33 begin_count: Mutex::new(0), 34 commit_count: Mutex::new(0), 35 rollback_count: Mutex::new(0), 36 query_queue: Mutex::new(VecDeque::new()), 37 exec_queue: Mutex::new(VecDeque::new()), 38 } 39 } 40 41 fn with_query_rule(mut self, needle: &str, response: &str) -> Self { 42 self.query_rules 43 .push((needle.to_string(), response.to_string())); 44 self 45 } 46 47 fn with_query_failure(mut self, needle: &str) -> Self { 48 self.fail_query_contains = Some(needle.to_string()); 49 self 50 } 51 52 fn with_exec_failure(mut self, needle: &str) -> Self { 53 self.fail_exec_contains = Some(needle.to_string()); 54 self 55 } 56 57 fn with_begin_failure(mut self) -> Self { 58 self.fail_begin = true; 59 self 60 } 61 62 fn with_commit_failure(mut self) -> Self { 63 self.fail_commit = true; 64 self 65 } 66 } 67 68 impl SqlExecutor for PatternExecutor { 69 fn exec(&self, sql: &str, _params_json: &str) -> Result<ExecOutcome, SqlError> { 70 if let Some(result) = self.exec_queue.lock().expect("exec queue lock").pop_front() { 71 return result; 72 } 73 if let Some(needle) = &self.fail_exec_contains { 74 if sql.contains(needle) { 75 return Err(SqlError::InvalidQuery(String::from("forced exec failure"))); 76 } 77 } 78 Ok(ExecOutcome { 79 changes: 1, 80 last_insert_id: 1, 81 }) 82 } 83 84 fn query_raw(&self, sql: &str, _params_json: &str) -> Result<String, SqlError> { 85 if let Some(result) = self 86 .query_queue 87 .lock() 88 .expect("query queue lock") 89 .pop_front() 90 { 91 return result; 92 } 93 if let Some(needle) = &self.fail_query_contains { 94 if sql.contains(needle) { 95 return Err(SqlError::InvalidQuery(String::from("forced query failure"))); 96 } 97 } 98 for (needle, response) in &self.query_rules { 99 if sql.contains(needle) { 100 return Ok(response.clone()); 101 } 102 } 103 Ok(String::from("[]")) 104 } 105 106 fn begin(&self) -> Result<(), SqlError> { 107 *self.begin_count.lock().expect("begin count lock") += 1; 108 if self.fail_begin { 109 return Err(SqlError::InvalidQuery(String::from("forced begin failure"))); 110 } 111 Ok(()) 112 } 113 114 fn commit(&self) -> Result<(), SqlError> { 115 *self.commit_count.lock().expect("commit count lock") += 1; 116 if self.fail_commit { 117 return Err(SqlError::InvalidQuery(String::from( 118 "forced commit failure", 119 ))); 120 } 121 Ok(()) 122 } 123 124 fn rollback(&self) -> Result<(), SqlError> { 125 *self.rollback_count.lock().expect("rollback count lock") += 1; 126 Ok(()) 127 } 128 } 129 130 fn assert_sql_error_code<T: core::fmt::Debug>(result: Result<T, SqlError>, code: &str) { 131 let err = result.unwrap_err(); 132 assert_eq!(err.code(), code); 133 } 134 135 fn backup_with_versions(format_version: &str, replica_db_version: &str) -> DatabaseBackup { 136 DatabaseBackup { 137 format_version: format_version.to_string(), 138 replica_db_version: replica_db_version.to_string(), 139 schema: Vec::new(), 140 migrations: Vec::new(), 141 data: Vec::new(), 142 } 143 } 144 145 #[test] 146 fn backup_public_api_error_paths_cover_library_instantiations() { 147 let schema_query = "select type, name, tbl_name as table_name, sql from sqlite_master"; 148 149 let executor = PatternExecutor::new().with_query_failure(schema_query); 150 assert_sql_error_code(export_database_backup(&executor), "ERR_INVALID_QUERY"); 151 152 let schema_rows = json!([ 153 { 154 "type": "table", 155 "name": "tb_a", 156 "table_name": "tb_a", 157 "sql": "CREATE TABLE tb_a (id TEXT);" 158 } 159 ]) 160 .to_string(); 161 let executor = PatternExecutor::new() 162 .with_query_rule(schema_query, &schema_rows) 163 .with_query_failure("SELECT * FROM \"tb_a\";"); 164 assert_sql_error_code(export_database_backup(&executor), "ERR_INVALID_QUERY"); 165 166 let executor = PatternExecutor::new().with_query_failure(schema_query); 167 assert_sql_error_code(export_database_backup_json(&executor), "ERR_INVALID_QUERY"); 168 169 let executor = PatternExecutor::new().with_query_rule(schema_query, "[]"); 170 let backup_json = export_database_backup_json(&executor).expect("backup json success"); 171 assert!(backup_json.contains("\"schema\":[]")); 172 173 let executor = PatternExecutor::new(); 174 let backup = backup_with_versions("0.0.1", REPLICA_DB_VERSION); 175 assert_sql_error_code( 176 restore_database_backup(&executor, &backup), 177 "ERR_INVALID_ARGUMENT", 178 ); 179 180 let backup = backup_with_versions(DATABASE_BACKUP_VERSION, REPLICA_DB_VERSION); 181 let executor = PatternExecutor::new().with_exec_failure("PRAGMA foreign_keys = OFF;"); 182 assert_sql_error_code( 183 restore_database_backup(&executor, &backup), 184 "ERR_INVALID_QUERY", 185 ); 186 187 let executor = PatternExecutor::new().with_begin_failure(); 188 assert_sql_error_code( 189 restore_database_backup(&executor, &backup), 190 "ERR_INVALID_QUERY", 191 ); 192 193 let executor = 194 PatternExecutor::new().with_query_failure("select type, name from sqlite_master"); 195 assert_sql_error_code( 196 restore_database_backup(&executor, &backup), 197 "ERR_INVALID_QUERY", 198 ); 199 200 let executor = PatternExecutor::new().with_commit_failure(); 201 assert_sql_error_code( 202 restore_database_backup(&executor, &backup), 203 "ERR_INVALID_QUERY", 204 ); 205 206 let executor = PatternExecutor::new().with_exec_failure("PRAGMA foreign_keys = ON;"); 207 assert_sql_error_code( 208 restore_database_backup(&executor, &backup), 209 "ERR_INVALID_QUERY", 210 ); 211 } 212 213 #[test] 214 fn backup_public_api_insert_and_parse_failures_cover_library_instantiations() { 215 let backup = DatabaseBackup { 216 format_version: DATABASE_BACKUP_VERSION.to_string(), 217 replica_db_version: REPLICA_DB_VERSION.to_string(), 218 schema: vec![SchemaEntry { 219 object_type: String::from("table"), 220 name: String::from("tb_a"), 221 table_name: Some(String::from("tb_a")), 222 sql: Some(String::from("CREATE TABLE tb_a (id TEXT);")), 223 }], 224 migrations: Vec::new(), 225 data: vec![TableData { 226 name: String::from("tb_a"), 227 rows: vec![{ 228 let mut row = Map::new(); 229 row.insert(String::from("id"), Value::from("1")); 230 row 231 }], 232 }], 233 }; 234 235 let executor = PatternExecutor::new().with_exec_failure("INSERT INTO \"tb_a\""); 236 assert_sql_error_code( 237 restore_database_backup(&executor, &backup), 238 "ERR_INVALID_QUERY", 239 ); 240 241 let schema_query = "select type, name, tbl_name as table_name, sql from sqlite_master"; 242 let executor = PatternExecutor::new().with_query_rule(schema_query, "{"); 243 assert_sql_error_code(export_database_backup(&executor), "ERR_SERIALIZATION"); 244 245 let schema_rows = json!([ 246 { 247 "type": "table", 248 "name": "tb_a", 249 "table_name": "tb_a", 250 "sql": "CREATE TABLE tb_a (id TEXT);" 251 } 252 ]) 253 .to_string(); 254 let executor = PatternExecutor::new() 255 .with_query_rule(schema_query, &schema_rows) 256 .with_query_rule("SELECT * FROM \"tb_a\";", "{"); 257 assert_sql_error_code(export_database_backup(&executor), "ERR_SERIALIZATION"); 258 259 let null_name_rows = json!([ 260 { 261 "type": "table", 262 "name": null, 263 "table_name": "tb_a", 264 "sql": "CREATE TABLE tb_a (id TEXT);" 265 } 266 ]) 267 .to_string(); 268 let executor = PatternExecutor::new().with_query_rule(schema_query, &null_name_rows); 269 let backup = export_database_backup(&executor).expect("backup with null-name schema rows"); 270 assert!(backup.schema.is_empty()); 271 } 272 273 #[test] 274 fn export_manifest_public_api_error_paths_cover_library_instantiations() { 275 let schema_query = "select type, name, tbl_name as table_name, sql from sqlite_master"; 276 277 let executor = PatternExecutor::new().with_query_failure(schema_query); 278 assert_sql_error_code(export_manifest(&executor), "ERR_INVALID_QUERY"); 279 280 let schema_rows = json!([ 281 { 282 "type": "table", 283 "name": "tb_a", 284 "table_name": "tb_a", 285 "sql": "CREATE TABLE tb_a (id TEXT);" 286 } 287 ]) 288 .to_string(); 289 290 let executor = PatternExecutor::new() 291 .with_query_rule(schema_query, &schema_rows) 292 .with_query_failure("select count(1) as count from \"tb_a\""); 293 assert_sql_error_code(export_manifest(&executor), "ERR_INVALID_QUERY"); 294 295 let executor = PatternExecutor::new() 296 .with_query_rule(schema_query, &schema_rows) 297 .with_query_rule("select count(1) as count from \"tb_a\"", "{"); 298 assert_sql_error_code(export_manifest(&executor), "ERR_SERIALIZATION"); 299 300 let executor = PatternExecutor::new() 301 .with_query_rule(schema_query, &schema_rows) 302 .with_query_rule("select count(1) as count from \"tb_a\"", "[]"); 303 let manifest = export_manifest(&executor).expect("manifest success"); 304 assert_eq!(manifest.table_counts.len(), 1); 305 }