open_options.rs (4967B)
1 use std::fs; 2 use std::time::Duration; 3 4 use radroots_storage::event::SourceGeneration; 5 use radroots_storage::status::WriterPolicy; 6 use radroots_storage_sqlite::{Error, OpenMode, OpenOptions, Paths}; 7 8 #[test] 9 fn paths_derive_only_runtime_and_private_database_ownership() { 10 let directory = tempfile::tempdir().expect("temporary directory"); 11 let paths = Paths::from_directory(directory.path()).expect("owned paths"); 12 13 assert_eq!(paths.runtime(), directory.path().join("runtime.sqlite")); 14 assert_eq!(paths.private(), directory.path().join("private.sqlite")); 15 assert!(!format!("{paths:?}").contains("studio.sqlite")); 16 } 17 18 #[test] 19 fn paths_reject_relative_traversal_and_wrong_owned_names() { 20 assert!(matches!( 21 Paths::from_directory("relative"), 22 Err(Error::InvalidPath(_)) 23 )); 24 25 let directory = tempfile::tempdir().expect("temporary directory"); 26 assert!(matches!( 27 Paths::from_files( 28 directory.path().join("../runtime.sqlite"), 29 directory.path().join("private.sqlite"), 30 ), 31 Err(Error::InvalidPath(_)) 32 )); 33 assert!(matches!( 34 Paths::from_files( 35 directory.path().join("studio.sqlite"), 36 directory.path().join("private.sqlite"), 37 ), 38 Err(Error::UnexpectedFileName { .. }) 39 )); 40 assert!(matches!( 41 Paths::from_files("/", directory.path().join("private.sqlite")), 42 Err(Error::UnexpectedFileName { .. }) 43 )); 44 } 45 46 #[test] 47 fn create_mode_accepts_missing_files_but_existing_modes_do_not() { 48 let directory = tempfile::tempdir().expect("temporary directory"); 49 let paths = Paths::from_directory(directory.path()).expect("owned paths"); 50 51 OpenOptions::new(paths.clone(), OpenMode::Create) 52 .validate_filesystem() 53 .expect("create plan"); 54 assert!(matches!( 55 OpenOptions::new(paths.clone(), OpenMode::ReadOnly).validate_filesystem(), 56 Err(Error::MissingFile(_)) 57 )); 58 assert!(matches!( 59 OpenOptions::new(paths, OpenMode::ReadWriteExisting).validate_filesystem(), 60 Err(Error::MissingFile(_)) 61 )); 62 } 63 64 #[test] 65 fn existing_modes_accept_only_regular_owned_files() { 66 let directory = tempfile::tempdir().expect("temporary directory"); 67 let paths = Paths::from_directory(directory.path()).expect("owned paths"); 68 fs::write(paths.runtime(), []).expect("runtime file"); 69 fs::write(paths.private(), []).expect("private file"); 70 71 for mode in [OpenMode::ReadOnly, OpenMode::ReadWriteExisting] { 72 OpenOptions::new(paths.clone(), mode) 73 .validate_filesystem() 74 .expect("existing plan"); 75 } 76 } 77 78 #[test] 79 fn options_fix_connection_invariants_and_bound_busy_timeout() { 80 let directory = tempfile::tempdir().expect("temporary directory"); 81 let paths = Paths::from_directory(directory.path()).expect("owned paths"); 82 let read_only = OpenOptions::new(paths.clone(), OpenMode::ReadOnly); 83 assert_eq!(read_only.busy_timeout(), Duration::from_secs(5)); 84 assert!(read_only.foreign_keys_enabled()); 85 assert!(!read_only.wal_enabled()); 86 assert_eq!(read_only.writer_policy(), WriterPolicy::NoWriter); 87 assert!(!read_only.mode().is_writable()); 88 assert!(!read_only.mode().may_create()); 89 90 let create = OpenOptions::new(paths, OpenMode::Create) 91 .with_busy_timeout(Duration::from_secs(30)) 92 .expect("bounded timeout"); 93 assert_eq!(create.busy_timeout(), Duration::from_secs(30)); 94 assert!(create.foreign_keys_enabled()); 95 assert!(create.wal_enabled()); 96 assert_eq!(create.writer_policy(), WriterPolicy::AdvisoryProcessLock); 97 assert!(create.mode().is_writable()); 98 assert!(create.mode().may_create()); 99 100 for invalid in [Duration::ZERO, Duration::from_secs(61)] { 101 assert!(matches!( 102 OpenOptions::new(create.paths().clone(), OpenMode::Create).with_busy_timeout(invalid), 103 Err(Error::InvalidBusyTimeout { .. }) 104 )); 105 } 106 107 let generation = SourceGeneration::new([9; 32]).expect("source generation"); 108 let bootstrapped = OpenOptions::new(create.paths().clone(), OpenMode::Create) 109 .with_source_generation(generation, 42) 110 .expect("source generation bootstrap"); 111 assert_eq!(bootstrapped.source_generation(), Some(generation)); 112 assert_eq!( 113 bootstrapped.source_generation_created_at_unix_ms(), 114 Some(42) 115 ); 116 assert!(matches!( 117 OpenOptions::new(create.paths().clone(), OpenMode::Create) 118 .with_source_generation(generation, 0), 119 Err(Error::InvalidSourceGenerationTimestamp { actual: 0 }) 120 )); 121 let beyond_sqlite_integer = u64::try_from(i64::MAX).expect("positive i64 maximum") + 1; 122 assert!(matches!( 123 OpenOptions::new(create.paths().clone(), OpenMode::Create) 124 .with_source_generation(generation, beyond_sqlite_integer), 125 Err(Error::InvalidSourceGenerationTimestamp { actual }) 126 if actual == beyond_sqlite_integer 127 )); 128 }