lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

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 }